Share
Embed
Transcript
108 slides
-
iOSDC Japan 2019 2019 9/6(Fri) 13:30~14:00 TrackA Swift ΫϦʔϯίʔυ Ξυϕϯνϟʔɹ
~ʑͷۤΛΓӽ͑ɺ͔֬ͳબΛ͢ΔͨΊʹ~ ը૾ͷϦϯΫ -
ୡਓͨͪᐌ͘ ͻͱͭͷ͜ͱΛߦͬͯҙਤ͕໌֬ ޠΛಡΉΑ͏ʹಡΊΔ ΤϨΨϯτͰޮ͕ྑ͍ ࣄ࣮ʹଈ͍ͯ͠ͳΕͳΒͳ͍ ҙຯΛ໊࣋ͬͨલ͕͋Δ ґଘੑ࠷ݶͰ໌֬ʹఆٛ͞Ε͍ͯΔ ಡΉͷʹେ͖ͳྗ͕ඞཁͳ͍ ڻ͖͕ͳ͍ ॏෳΛݮΒ͠ɺදݱྗΛߴΊɺ
୯७ͳநԽΛૣ͍࣌ظʹ࡞Γ͋͛Δ খ͖͜͞ͱྑ͖͜ͱ ݪ࡞ऀҎ֎ͷਓʹಡΉ͜ͱ͕Ͱ͖ɺ ֦ுͰ͖Δίʔυ ৗʹ୭͔͕ؾΓΛ࣋ͬͯॻ͍͍ͯΔΑ͏ʹݟ͑Δ Clean Code ΞδϟΠϧιϑτΣΞୡਓͷٕ Robert C. Martin(ஶ), ՖҪࢤੜ(༁) -
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 -
δΣωϦΫε (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 -
Protocol ίϯύΠϧ࣌ δΣωϦΫεͷܕ੍ ܕ҆શɾ࠷దԽ Static ࣮ߦ࣌ ܕͱͯ͠༻ Dynamic Dispatch Dynamic
func F<P: Protocol> (onlyOneType: P) let types:[Protocol] = [A(), B(), …] -
Protocol ίϯύΠϧ࣌ δΣωϦΫεͷܕ੍ ܕ҆શɾ࠷దԽ Static ࣮ߦ࣌ ܕͱͯ͠༻ Dynamic Dispatch Dynamic
func F<P: Protocol> (onlyOneType: P) let types:[Protocol] = [A(), B(), …] -
Protocol ίϯύΠϧ࣌ δΣωϦΫεͷܕ੍ ܕ҆શɾ࠷దԽ Static ࣮ߦ࣌ ܕͱͯ͠༻ Dynamic Dispatch Dynamic
func F<P: Protocol> (onlyOneType: P) let types:[Protocol] = [A(), B(), …] -
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 -
use value types, then if you need polymorphism, make them
conform to protocols. Avoid classes. https://twitter.com/cocoaphony/status/1104114233288151043 ϙϦϞʔϑΟζϜ ಉ໊͡લͷϝιουΛෳͷΫϥεͰ༻Ͱ͖ΔΑ͏ʹ͠ɺͦͷϝ ιουΛ௨ͯ͠ɺ҉తʹෳͷΠϯελϯεͷಈ࡞ΛΓସ͑Δ ͜ͱ͕Ͱ͖ΔΑ͏ʹ͢Δ͜ͱ -
…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 -
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 -
struct Item: Codable { let id: Int let name: String
} struct User: Codable { let id: Int let name: String } struct Client { let baseURL = URL(string: “…”)! } -
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() } } -
extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)
-> Void) { let urlRequest = URLRequest(url: baseURL .appendingPathComponent("items") .appendingPathComponent("\(id)") ) } } -
extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)
-> Void) { … let session = URLSession.shared session.dataTask(with: urlRequest) { … }.resume() -
extension Client { func fetchItem(id: Int, completion: @escaping (Result<Item, Error>)
-> Void) { … let decoder = JSONDecoder() completion(Result { try decoder.decode(Item.self, from: data) }) } } -
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() } } -
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() } } -
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) } } -
extension Client { func fetch<Model: Decodable>( _: Model.Type, } }
Client().fetch(id: 1) { … } Client().fetch(id: 1) { (result: Result<User, Error>) … } -
extension Client { func fetch<Model: Decodable>( …) { let urlRequest
= URLRequest(url: baseURL .appendingPathComponent(“???”) .appendingPathComponent(“\(id)") ) } } -
extension User: Fetchable { static var apiBase: String { return
"users" } } extension Item: Fetchable { static var apiBase: String { return “items" } } -
extension User: Fetchable { static var apiBase: String { return
"users" } } extension Item: Fetchable { static var apiBase: String { return “items" } } -
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() } } -
protocol URLSessionProtocol { func dataTask(with request: URLRequest, completionHandler: @escaping (Data?,
URLResponse?, Error?) -> Void) -> URLSessionDataTask } extension URLSession: URLSessionProtocol {} -
protocol URLSessionProtocol { func dataTask( with request: URLRequest, completionHandler: @escaping
(Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask } -
protocol Transport { func send(request: URLRequest, completion: @escaping (Result<Data, Error>)
-> Void) } URLRequest -> Result<Data, Error> -
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()) -
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() } } -
extension URLSession: Transport { func send(request: URLRequest, completion: @escaping (Result<Data,
Error>) -> Void) { } } Retroactive Modeling -
struct Client { init( transport: Transport = URLSession.shared ) {
… } } let client = Client() -
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) } } -
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(…)) … -
protocol Request { associatedtype Response: Fetchable var baseURL: URL {
get } var path: String { get } var httpMethod: HTTPMethod { get } var parameters: [String: Any] { get } } -
protocol Request { associatedtype Response: Fetchable var baseURL: URL {
get } var path: String { get } var httpMethod: HTTPMethod { get } var parameters: [String: Any] { get } } -
protocol Transport { func fetch(request: Request) } ※associatedtype·ͨSelfΛ͏Protocol δΣωϦΫεͷ੍ͱͯ͠༻Ͱ͖Δ͕ ܕͱͯ͠༻Ͱ͖ͳ͘ͳΔ
PAT(Protocol With AssociatedType)※ -
extension Client { func multiFetch<R: Request>(requests: [R]) { for request
in requests { transport.send(request: request) } } } -
protocol Request { func makeURLRequest() throws -> URLRequest } protocol
Transport { func send(request: Request, completion: @escaping (Result<Data, Error>) -> Void) } -
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 } } -
struct Item: Codable { let id: Int let name: String
} struct User: Codable { let id: Int let name: String } -
struct Item: Codable { let id: String let name: String
} func fetch<Model: Fetchable>( _: Model.Type, id: Int, … ❌ -
protocol Identifiable: Codable, Hashable { associatedtype Value var value: Value
{ get } init(value: Value) } extension Identifiable { init(_ value: Value) { self.init(value: value) } } -
protocol Identifiable: Codable, Hashable { associatedtype Value var value: Value
{ get } init(value: Value) } -
struct Item: Codable { struct ID: Identifiable { let value:
String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID } -
struct Item: Codable { struct ID: Identifiable { let value:
String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID } -
struct Item: Codable { struct ID: Identifiable { let value:
String } let id: ID } struct User: Codable { struct ID: Identifiable { let value: Int } let id: ID } -
struct Identifier<Model, Value> where Value: Codable & Hashable { let
value: Value init(_ value: Value) { self.value = value } } extension Identifier: Codable, Hashable { … } -
protocol Fetchable: Decodable { static var apiBase: String { get
} associatedtype IDType: Codable & Hashable typealias ID = Identifier<Self, IDType> var id: ID { get } } -
struct Item: Codable { typealias ID = Identifier<Item, String> var
id: ID { get } } struct User: Codable { typealias ID = Identifier<User, Int> var id: ID { get } } -
protocol Fetchable: Decodable { associatedtype IDType: Codable typealias ID =
Identifier<Self, IDType> var id: ID { get } static var apiBase: String { get } } ࣝผࢠ(Identifier)Λ࣋ͭ + API͔Βσʔλऔಘ -
struct Item: Codable { typealias = Identifier<Item, String> let id:
ID } struct User: Codable { typealias = Identifier<User, String> let id: ID } -
protocol Identifiable: Codable { associatedtype IDType: Codable & Hashable typealias
ID = Identifier<Self, IDType> var id: ID { get } } -
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͔Βσʔλऔಘ -
struct Item: Identifiable { typealias IDType = String let id:
ID … } struct User: Identifiable { typealias IDType = Int let id: ID … } -
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 -
Ձମܥ˞͕ͦ͜ඪ Ͱ͋Γɺ͜ͷຊͷ ςʔϚͩͬͨͷͰ͢ɻ Clean Code ΞδϟΠϧιϑτΣΞୡਓͷٕ Robert C. Martin(ஶ), ՖҪࢤੜ(༁)
※ࢥߟɾߦಈ͢Δࡍͷ͋Δಛఆͷ ྖҬʹ͓͚ΔՁ؍ɾஅج४ -
Configurable types ~ࣅͯඇͳΔίʔυΛڞ௨ʹѻ͏~ https://qiita.com/shiz/items/849e483338bd568cb6ab ۩ମతͳίʔυ͔Β࢝ΊΑ ~ࠓͷΛղܾ͠ɺδΣωϦοΫͳίʔυΛݟग़͢~ https://qiita.com/shiz/items/5755a35887bcb7897464 มԽʹରԠ͠ຊ࣭Λ࠶ൃݟ͢Δ https://qiita.com/shiz/items/7b3bda7c2d84c5a83c0f Kickstarter͔ΒֶͿ
~ΑΓྑ͍։ൃΛࢦͨ͢ΊͷΈͱʁ~ https://qiita.com/shiz/items/79c7b39f94f32e548df3 ProtocolߏจͷೖΕҎ্ͷଘࡏͰ͋Δ https://qiita.com/shiz/items/da71d547b59c757cca94 ิهࣄ -
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) ࢀߟϦϯΫू -
Modern Swift API Design The swift programming language swift 5.1
Exrtesions Protocols are more than Bags of Syntax Configurable types in Swift ࢀߟϦϯΫू