Upgrade to Pro — share decks privately, control downloads, hide ads and more …

swift_clean_code.pdf

 swift_clean_code.pdf

Presentation about swift clean code

[email protected]

September 06, 2019
Tweet

Other Decks in Programming

Transcript

  1. iOSDC Japan 2019 2019 9/6(Fri) 13:30~14:00 TrackA Swift ΫϦʔϯίʔυ Ξυϕϯνϟʔɹ

    ~೔ʑͷۤ೰Λ৐Γӽ͑ɺ͔֬ͳબ୒Λ͢ΔͨΊʹ~ ը૾ͷϦϯΫ
  2. ୡਓͨͪᐌ͘ ͻͱͭͷ͜ͱΛߦͬͯҙਤ͕໌֬ ෺ޠΛಡΉΑ͏ʹಡΊΔ ΤϨΨϯτͰޮ཰͕ྑ͍ ࣄ࣮ʹଈ͍ͯ͠ͳΕ͹ͳΒͳ͍ ҙຯΛ໊࣋ͬͨલ͕͋Δ ґଘੑ͸࠷௿ݶͰ໌֬ʹఆٛ͞Ε͍ͯΔ ಡΉͷʹେ͖ͳ౒ྗ͕ඞཁͳ͍ ڻ͖͕ͳ͍ ॏෳΛݮΒ͠ɺදݱྗΛߴΊɺ

    ୯७ͳந৅ԽΛૣ͍࣌ظʹ࡞Γ͋͛Δ খ͖͜͞ͱ͸ྑ͖͜ͱ ݪ࡞ऀҎ֎ͷਓʹ΋ಡΉ͜ͱ͕Ͱ͖ɺ ֦ுͰ͖Δίʔυ ৗʹ୭͔͕ؾ഑ΓΛ࣋ͬͯॻ͍͍ͯΔΑ͏ʹݟ͑Δ Clean Code ΞδϟΠϧιϑτ΢ΣΞୡਓͷٕ Robert C. Martin(ஶ), ՖҪࢤੜ(༁)
  3. Kickstarter ios-oss https://github.com/kickstarter/ios-oss POINT•FREE https://www.pointfree.co/ objc.io iOS at Kickstarter https://talk.objc.io/collections/ios-at-kickstarter

    How to Control the World https://vimeo.com/291588126 Reactive view models, simplified https://www.youtube.com/watch?v=uTLG_LgjWGA Kickstarter ios-ossߏ੒ͷղઆ https://note.mu/yimajo/n/ne44c7945279a RxSwiftݚڀಡຊ3 ViewModelઃܭύλʔϯೖ໳ https://booth.pm/ja/items/1223536 Kickstarter-iOSͷViewModelͷ࡞Γํ͕΢Ϛ͔ͬͨ https://qiita.com/muukii0803/items/045b12405f7acff1a9fd
  4. δΣωϦΫε (generics) GVODTXBQ5XP7BMVFT 5 @BJOPVU 5 @CJOPVU 5 GVODTXBQ5XP4USJOHT @BJOPVU4USJOH

    @CJOPVU4USJOH GVODTXBQ5XP%PVCMFT @BJOPVU%PVCMF @CJOPVU%PVCMF GVODTXBQ5XP*OUT @BJOPVU*OU @CJOPVU*OU https://docs.swift.org/swift-book/LanguageGuide/Generics.html
  5. if you want to write a generalized sort or binary

    search…Don’t start with a class. Start with a protocol. Protocol-Oriented Programming in Swift WWDC2015
  6. use value types, then if you need polymorphism, make them

    conform to protocols. Avoid classes. https://twitter.com/cocoaphony/status/1104114233288151043 ϙϦϞʔϑΟζϜ ಉ໊͡લͷϝιουΛෳ਺ͷΫϥεͰ࢖༻Ͱ͖ΔΑ͏ʹ͠ɺͦͷϝ ιουΛ௨ͯ͠ɺ҉໧తʹෳ਺ͷΠϯελϯεͷಈ࡞Λ੾Γସ͑Δ ͜ͱ͕Ͱ͖ΔΑ͏ʹ͢Δ͜ͱ
  7. …we considered a varied number of concrete types first. …we’re

    thinking about a kind of protocol that could join them all together. …it’s important to think of things as this way around. To start with some concrete types, and then try and unify them with a protocol. Swift Generics (Expanded) WWDC2018
  8. Don’t Literally Start With a Protocol Start with a concrete

    use cases Discover a need for generic code Try to compose solutions from existing protocols first Consider a generic type instead of a protocol Modern Swift API Design WWDC2019
  9. struct Item: Codable { let id: Int let name: String

    } struct User: Codable { let id: Int let name: String } struct Client { let baseURL = URL(string: “…”)! }
  10. extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)

    -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent(“items") .appendingPathComponent("\(id)") ) let session = URLSession.shared session.dataTask(with: urlRequest) { (data, _, error) in if let error = error { completion(.failure(error)) } else if let data = data { let decoder = JSONDecoder() completion(Result { try decoder.decode(Item.self, from: data) }) } }.resume() } }
  11. extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)

    -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent("items") .appendingPathComponent("\(id)") ) } }
  12. extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)

    -> Void) { … let session = URLSession.shared session.dataTask(with: urlRequest) { … }.resume()
  13. extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)

    -> Void) { … let decoder = JSONDecoder() completion(Result { try decoder.decode(Item.self, from: data) }) } }
  14. extension Client { func fetchUser(id: Int, completion: @escaping (Result<User, Error>)

    -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent(“users") .appendingPathComponent("\(id)") ) let session = URLSession.shared session.dataTask(with: urlRequest) { (data, _, error) in if let error = error { completion(.failure(error)) } else if let data = data { let decoder = JSONDecoder() completion(Result { try decoder.decode(User.self, from: data) }) } }.resume() } }
  15. extension Client { func fetch Item(id: Int, completion: @escaping (Result<

    Item, Error>) -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent(" items") .appendingPathComponent("\(id)") ) let session = URLSession.shared session.dataTask(with: urlRequest) { (data, _, error) in if let error = error { completion(.failure(error)) } else if let data = data { let decoder = JSONDecoder() completion(Result { try decoder.decode( Item.self, from: data) }) } }.resume() } } extension Client { func fetch User(id: Int, completion: @escaping (Result< User, Error>) -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent(" users") .appendingPathComponent("\(id)") ) let session = URLSession.shared session.dataTask(with: urlRequest) { (data, _, error) in if let error = error { completion(.failure(error)) } else if let data = data { let decoder = JSONDecoder() completion(Result { try decoder.decode( User.self, from: data) }) } }.resume() } }
  16. extension Client { func fetch<Model: Decodable>( _: Model.Type, id: Int,

    completion: @escaping (Result<Model, Error>) -> Void) { … let decoder = JSONDecoder() completion(Result { try decoder.decode(Model.self, from: data) } }
  17. extension Client { func fetch<Model: Decodable>( _: Model.Type, } }

    Client().fetch(id: 1) { … } Client().fetch(id: 1) { (result: Result<User, Error>) … }
  18. extension Client { func fetch<Model: Decodable>( …) { let urlRequest

    = URLRequest(url: baseURL .appendingPathComponent(“???”) .appendingPathComponent(“\(id)") ) } }
  19. extension User: Fetchable { static var apiBase: String { return

    "users" } } extension Item: Fetchable { static var apiBase: String { return “items" } }
  20. extension User: Fetchable { static var apiBase: String { return

    "users" } } extension Item: Fetchable { static var apiBase: String { return “items" } }
  21. extension Client { func fetch<Model: Decodable>( _: Model.Type, id: Int,

    completion: @escaping (Result<Model, Error>) -> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent(Model.apiBase) .appendingPathComponent(“\(id)") ) let session = URLSession.shared session.dataTask(with: urlRequest) { … }.resume() } }
  22. protocol URLSessionProtocol { func dataTask(with request: URLRequest, completionHandler: @escaping (Data?,

    URLResponse?, Error?) -> Void) -> URLSessionDataTask } extension URLSession: URLSessionProtocol {}
  23. struct Client { let transport: Transport init(transport: Transport) { self.transport

    = transport } } func fetch<Model: Fetchable>( transport.send(request: urlRequest) { … return try decoder.decode(Model.self, from: result.get())
  24. extension URLSession: Transport { func send(request: URLRequest, completion: @escaping (Result<Data,

    Error>) -> Void) { dataTask(with: request) { (data, _, error) in if let error = error { return completion(.failure(error)) } if let data = data { completion(.success(data)) } }.resume() } }
  25. struct TransportWithToken: Transport { let base: Transport let token: String

    func send(request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) { var newRequest = request newRequest.addValue(token, forHTTPHeaderField: "token") base.fetch(request: newRequest, completion: completion) } }
  26. final class TestTransport: Transport { var requests: [Any] = []

    var responseData: [Data] init(responseData: [Data]) { self.responseData = responseData } func send(…) { requests.append(request) guard let data = responseData.removeFirst() else { return completion(.success(data)) } completion(.failure(…)) …
  27. protocol Request { associatedtype Response: Fetchable var baseURL: URL {

    get } var path: String { get } var httpMethod: HTTPMethod { get } var parameters: [String: Any] { get } }
  28. protocol Request { associatedtype Response: Fetchable var baseURL: URL {

    get } var path: String { get } var httpMethod: HTTPMethod { get } var parameters: [String: Any] { get } }
  29. extension Client { func multiFetch<R: Request>(requests: [R]) { for request

    in requests { transport.send(request: request) } } }
  30. protocol Request { func makeURLRequest() throws -> URLRequest } protocol

    Transport { func send(request: Request, completion: @escaping (Result<Data, Error>) -> Void) }
  31. struct TransportWithTokenRequest: Request { let base: Request let token: String

    func makeURLRequest() throws -> URLRequest { var newRequest = try base.makeURLRequest() newRequest.addValue(token, forHTTPHeaderField: "token") return newRequest } }
  32. struct Item: Codable { let id: Int let name: String

    } struct User: Codable { let id: Int let name: String }
  33. struct Item: Codable { let id: String let name: String

    } func fetch<Model: Fetchable>( _: Model.Type, id: Int, … ❌
  34. protocol Identifiable: Codable, Hashable { associatedtype Value var value: Value

    { get } init(value: Value) } extension Identifiable { init(_ value: Value) { self.init(value: value) } }
  35. struct Item: Codable { struct ID: Identifiable { let value:

    String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID }
  36. struct Item: Codable { struct ID: Identifiable { let value:

    String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID }
  37. struct Item: Codable { struct ID: Identifiable { let value:

    String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID }
  38. struct Identifier<Model, Value> where Value: Codable & Hashable { let

    value: Value init(_ value: Value) { self.value = value } } extension Identifier: Codable, Hashable { … }
  39. protocol Fetchable: Decodable { static var apiBase: String { get

    } associatedtype IDType: Codable & Hashable typealias ID = Identifier<Self, IDType> var id: ID { get } }
  40. struct Item: Codable { typealias ID = Identifier<Item, String> var

    id: ID { get } } struct User: Codable { typealias ID = Identifier<User, Int> var id: ID { get } }
  41. protocol Fetchable: Decodable { associatedtype IDType: Codable typealias ID =

    Identifier<Self, IDType> var id: ID { get } static var apiBase: String { get } } ࣝผࢠ(Identifier)Λ࣋ͭ + API͔Βσʔλऔಘ
  42. struct Item: Codable { typealias = Identifier<Item, String> let id:

    ID } struct User: Codable { typealias = Identifier<User, String> let id: ID }
  43. protocol Identifiable: Codable { associatedtype IDType: Codable & Hashable typealias

    ID = Identifier<Self, IDType> var id: ID { get } } protocol Fetchable: Identifiable { static var apiBase: String { get } } ࣝผࢠ(Identifier)Λ࣋ͭ IDΛ࢖ͬͯAPI͔Βσʔλऔಘ
  44. struct Item: Identifiable { typealias IDType = String let id:

    ID … } struct User: Identifiable { typealias IDType = Int let id: ID … }
  45. Don’t Literally Start With a Protocol Modern Swift API Design

    WWDC2019 Start with a concrete use cases Discover a need for generic code Try to compose solutions from existing protocols first Consider a generic type instead of a protocol
  46. Kickstarter ios-oss Star with a protocol A mockery of protocols

    That’s not a number From Problem to Solution Protocol-Oriented Programming in Swift Swift Generics (Expanded) ࢀߟϦϯΫू
  47. Modern Swift API Design The swift programming language swift 5.1

    Exrtesions Protocols are more than Bags of Syntax Configurable types in Swift ࢀߟϦϯΫू