Slide 1

Slide 1 text

ςετσʔλࣗಈੜ੒ํ๏ͷผղ ాதୡ໵ (@tattn) #potatotips 57

Slide 2

Slide 2 text

ాத ୡ໵ / ͨͳͨͭ (@tattn) • Yahoo!৐׵Ҋ಺ • iOSΞϓϦΤϯδχΞ @tattn @tanakasan2525 @tattn

Slide 3

Slide 3 text

Ϣχοτςετ༻ͷ
 ςετσʔλ

Slide 4

Slide 4 text

APIϨεϙϯεͷςετσʔλ͕΄͍͠ ϢχοτςετͰ MockAPIClient Λ࡞Δͱ͖ʹ σʔλΛ࡞Δͷ͕໘౗...

Slide 5

Slide 5 text

ViewModelͷςετ self.viewModel = ViewModel( trainStatusInfoAPI: self.trainStatusInfoAPI, registrationAPI: self.registrationAPI, congestionAPI: self.congestionAPI ) self.trainStatusInfoAPI.mock = TestData(...) self.registrationAPI.mock = TestData2(...) self.congestionAPI.mock = TestData3(...) self.viewModel.request() expect(self.viewModel.cellData.value).toEventually(equal(...)) ←͕͜͜໘౗

Slide 6

Slide 6 text

ςετσʔλͷॳظԽ TrainStatusInfo(statuses: [ Status(railway: Railway(information: Information( code: 0, name: "", trainStatuses: [ TrainStatus( impactRange: "impactRange", message: "message", statusTitle: "statusTitle", statusCode: "statusCode", updatedAt: "updatedAt", …, ) ], displayName: "displayName", webURL: "webURL", fooCode: 0, barCode: 1, bazName: "bazName", quxName: "quxName", quuxID: 2, corge: “corge", grault: “grault", …, ) ) )], …: Hoge(…)) ͜Μͳͷॻ͖ͨ͘ͳ͍
 ਓྨͷ࢓ࣄ͡Όͳ͍ (´ɾωɾʆ)

Slide 7

Slide 7 text

APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏ • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ
 σʔλΛࠩ͠ࠐΉ • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ
 ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

Slide 8

Slide 8 text

APIͷϨεϙϯεΛςετσʔλʹࠩ͠ସ͑Δํ๏ • protocolͰࠩ͠ସ͑ͨMockAPIΫϥΠΞϯτʹ
 σʔλΛࠩ͠ࠐΉ • URLSessionΛϋοΫͯ͠ɺϨεϙϯεΛ
 ༻ҙͨ͠JSONϑΝΠϧʹࠩ͠ସ͑Δ

Slide 9

Slide 9 text

ཚ਺ͰॳظԽͯ͠
 ςετσʔλʹ͢Δ

Slide 10

Slide 10 text

ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ • ϦϑϨΫγϣϯ
 
 


Slide 11

Slide 11 text

ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ • ϦϑϨΫγϣϯ • ✖ Mirror: ࢖͏લʹΠϯελϯεԽ͕ඞཁ (㱻)

Slide 12

Slide 12 text

ཚ਺ͰॳظԽ͢ΔͨΊͷΞϓϩʔν • ✖ ίʔυੜ੒: ΞϦ͚ͩͲPure SwiftͰ΍Γ͍ͨ • ϦϑϨΫγϣϯ • ✖ Mirror: ࢖͏લʹΠϯελϯεԽ͕ඞཁ (㱻) • Codable: ΠϯελϯεԽͳ͠Ͱ
 ܕͷߏ଄ΛಘΒΕΔ

Slide 13

Slide 13 text

https://github.com/tattn/ Randomizable

Slide 14

Slide 14 text

Randomizable struct A: Decodable, Randomizable { let string: String let int: Int let child: Child struct Child: Decodable, Randomizable { let url: URL let array: [Double] } } print(A.randomValue()) // A(string: "lgygox", int: Optional(7366465203208943133), child: Child(url: https://laoygisn.com, array: [4.67308519, 1.8293057, -6.64297, 7.797])) ↓ཚ਺ͰॳظԽͯ͘͠ΕΔ

Slide 15

Slide 15 text

Randomizable struct ParentType: Decodable, Randomizable { let customType: CustomType struct CustomType: Decodable, Randomizable { let animal: String static func randomValue() -> A.Child.ChildChild { return .init(animal: ["", "", ""].randomElement()!) } } } ↓ϥϯμϜͷϧʔϧΛΧελϚΠζͰ͖Δ // ParentType(customType: CustomType(animal: "")) print(ParentType.randomValue())

Slide 16

Slide 16 text

࣮૷

Slide 17

Slide 17 text

Randomizable public protocol Randomizable { static func randomValue() -> Self } public extension Randomizable where Self: Decodable { public static func randomValue() -> Self { let randomDecoder = RandomDecoder() return try! randomDecoder.decode(Self.self) } } Decodableʹconform͍ͯ͠Δ৔߹͸ϥϯμϜͷ࣮૷Λෆཁʹ

Slide 18

Slide 18 text

Randomizableͷඪ४αϙʔτ extension Bool: Randomizable {} extension Int: Randomizable {} extension Int8: Randomizable {} extension Int16: Randomizable {} extension Int32: Randomizable {} extension Int64: Randomizable {} extension UInt: Randomizable {} extension UInt8: Randomizable {} extension UInt16: Randomizable {} extension UInt32: Randomizable {} extension UInt64: Randomizable {} extension Float: Randomizable {} extension Double: Randomizable {} extension String: Randomizable {} extension Optional: Randomizable where Wrapped: Randomizable { public static func randomValue() -> Optional { return Bool.random() ? nil : Wrapped.randomValue() } } extension URL: Randomizable { public static func randomValue() -> URL { return URL(string: "https://\(String.defaultRandom()).com")! } }

Slide 19

Slide 19 text

RandomDecoderͷ࣮૷ import Foundation open class RandomDecoder: Decoder { open var codingPath: [CodingKey] open var userInfo: [CodingUserInfoKey: Any] = [:] public init(codingPath: [CodingKey] = []) { self.codingPath = codingPath } open func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { return KeyedDecodingContainer(KeyedContainer(decoder: self, codingPath: [])) } open func unkeyedContainer() throws -> UnkeyedDecodingContainer { return UnkeyedContanier(decoder: self) } open func singleValueContainer() throws -> SingleValueDecodingContainer { return SingleValueContanier(decoder: self) } func random() throws -> T { if let randomType = T.self as? Randomizable.Type { return randomType.randomValue() as! T } else { return try T(from: self) } } } extension RandomDecoder { open func decode(_ type: T.Type) throws -> T { return try T(from: self) } } extension RandomDecoder { private class KeyedContainer: KeyedDecodingContainerProtocol { private var decoder: RandomDecoder private(set) var codingPath: [CodingKey] init(decoder: RandomDecoder, codingPath: [CodingKey]) { self.decoder = decoder self.codingPath = codingPath } var allKeys: [Key] { return [] } func contains(_ key: Key) -> Bool { return true } func decodeNil(forKey key: Key) throws -> Bool { return Bool.random() } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { return .random() } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { return .defaultRamdom() } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { return .defaultRamdom() } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { return .defaultRamdom() } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { return .defaultRamdom() } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { return .defaultRamdom() } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { return .defaultRamdom() } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { return .defaultRamdom() } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { return .defaultRamdom() } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { return .defaultRamdom() } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { return .defaultRamdom() } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { return .defaultRamdom() } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { return .defaultRamdom() } func decode(_ type: String.Type, forKey key: Key) throws -> String { return .defaultRamdom() } func decode(_ type: T.Type, forKey key: Key) throws -> T { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return try decoder.random() } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return KeyedDecodingContainer(KeyedContainer(decoder: decoder, codingPath: [])) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return UnkeyedContanier(decoder: decoder) } func _superDecoder(forKey key: CodingKey = AnyCodingKey.super) throws -> Decoder { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return RandomDecoder() } func superDecoder() throws -> Decoder { return try _superDecoder() } func superDecoder(forKey key: Key) throws -> Decoder { return try _superDecoder(forKey: key) } } private class UnkeyedContanier: UnkeyedDecodingContainer { private var decoder: RandomDecoder private(set) var codingPath: [CodingKey] private(set) var count: Int? var isAtEnd: Bool { return currentIndex >= count! } private(set) var currentIndex: Int = 0 private var currentCodingPath: [CodingKey] { return decoder.codingPath + [AnyCodingKey(index: currentIndex)] } init(decoder: RandomDecoder) { self.decoder = decoder self.codingPath = decoder.codingPath count = Int.random(in: 2...4) } func ramdom(_ type: T.Type) throws -> T { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return T.defaultRamdom() } func decodeNil() throws -> Bool { return false } func decode(_ type: Bool.Type) throws -> Bool { return try ramdom(type) } func decode(_ type: Int.Type) throws -> Int { return try ramdom(type) } func decode(_ type: Int8.Type) throws -> Int8 { return try ramdom(type) } func decode(_ type: Int16.Type) throws -> Int16 { return try ramdom(type) } func decode(_ type: Int32.Type) throws -> Int32 { return try ramdom(type) } func decode(_ type: Int64.Type) throws -> Int64 { return try ramdom(type) } func decode(_ type: UInt.Type) throws -> UInt { return try ramdom(type) } func decode(_ type: UInt8.Type) throws -> UInt8 { return try ramdom(type) } func decode(_ type: UInt16.Type) throws -> UInt16 { return try ramdom(type) } func decode(_ type: UInt32.Type) throws -> UInt32 { return try ramdom(type) } func decode(_ type: UInt64.Type) throws -> UInt64 { return try ramdom(type) } func decode(_ type: Float.Type) throws -> Float { return try ramdom(type) } func decode(_ type: Double.Type) throws -> Double { return try ramdom(type) } func decode(_ type: String.Type) throws -> String { return try ramdom(type) } func decode(_ type: T.Type) throws -> T { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return try decoder.random() } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return KeyedDecodingContainer(KeyedContainer(decoder: decoder, codingPath: [])) } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return UnkeyedContanier(decoder: decoder) } func superDecoder() throws -> Decoder { decoder.codingPath.append(AnyCodingKey(index: currentIndex)) defer { decoder.codingPath.removeLast() currentIndex += 1 } return RandomDecoder(codingPath: decoder.codingPath) } } private class SingleValueContanier: SingleValueDecodingContainer { private var decoder: RandomDecoder var codingPath: [CodingKey] { return decoder.codingPath } init(decoder: RandomDecoder) { self.decoder = decoder } func decodeNil() -> Bool { return true } func decode(_ type: Bool.Type) throws -> Bool { return .defaultRamdom() } func decode(_ type: Int.Type) throws -> Int { return .defaultRamdom() } func decode(_ type: Int8.Type) throws -> Int8 { return .defaultRamdom() } func decode(_ type: Int16.Type) throws -> Int16 { return .defaultRamdom() } func decode(_ type: Int32.Type) throws -> Int32 { return .defaultRamdom() } func decode(_ type: Int64.Type) throws -> Int64 { return .defaultRamdom() } func decode(_ type: UInt.Type) throws -> UInt { return .defaultRamdom() } func decode(_ type: UInt8.Type) throws -> UInt8 { return .defaultRamdom() } func decode(_ type: UInt16.Type) throws -> UInt16 { return .defaultRamdom() } func decode(_ type: UInt32.Type) throws -> UInt32 { return .defaultRamdom() } func decode(_ type: UInt64.Type) throws -> UInt64 { return .defaultRamdom() } func decode(_ type: Float.Type) throws -> Float { return .defaultRamdom() } func decode(_ type: Double.Type) throws -> Double { return .defaultRamdom() } func decode(_ type: String.Type) throws -> String { return .defaultRamdom() } func decode(_ type: T.Type) throws -> T { return try decoder.random() } } }

Slide 20

Slide 20 text

RandomDecoderͷ࣮૷ͷϐοΫΞοϓ extension RandomDecoder { private class KeyedContainer: KeyedDecodingContainerProtocol { private var decoder: RandomDecoder private(set) var codingPath: [CodingKey] init(decoder: RandomDecoder, codingPath: [CodingKey]) { self.decoder = decoder self.codingPath = codingPath } func decodeNil(forKey key: Key) throws -> Bool { return .random() } func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { return .random() } func decode(_ type: Int.Type, forKey key: Key) throws -> Int { return .defaultRandom() } func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { return .defaultRandom() } func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { return .defaultRandom() } func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { return .defaultRandom() } func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { return .defaultRandom() } func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { return .defaultRandom() } func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { return .defaultRandom() } func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { return .defaultRandom() } func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { return .defaultRandom() } func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { return .defaultRandom() } func decode(_ type: Float.Type, forKey key: Key) throws -> Float { return .defaultRandom() } func decode(_ type: Double.Type, forKey key: Key) throws -> Double { return .defaultRandom() } func decode(_ type: String.Type, forKey key: Key) throws -> String { return .defaultRandom() } func decode(_ type: T.Type, forKey key: Key) throws -> T { decoder.codingPath.append(key) defer { decoder.codingPath.removeLast() } return try decoder.random() } ͱʹ͔͘ཚ਺Λฦͯ͠Δ͚ͩ

Slide 21

Slide 21 text

ͦͷଞ׆༻ํ๏ struct JSON: Codable, Randomizable { let value: String } let json = JSON.randomValue() let jsonData = try JSONEncoder().encode(json) let jsonString = String(data: jsonData, encoding: .utf8) ϥϯμϜͳJSONจࣈྻͷ࡞੒

Slide 22

Slide 22 text

Swift Package ManagerରԠ ίϚϯυϥΠϯπʔϧͰ׆༻Ͱ͖ͦ͏ͳͷͰɺ
 Swift Package ManagerʹରԠ͠·ͨ͠ import PackageDescription let package = Package( name: "Hoge", dependencies: [ .package(url: "https://github.com/tattn/Randomizable.git", from: “1.0.3"), .package(url: "https://github.com/tattn/MoreCodable.git", from: “0.2.2") ], targets: [ .target( name: “Hoge", dependencies: ["Randomizable", "MoreCodable"]) ] ) IUUQTHJUIVCDPNUBUUO.PSF$PEBCMF ͱͷ૊Έ߹Θ͕͓ͤ͢͢Ί

Slide 23

Slide 23 text

·ͱΊ

Slide 24

Slide 24 text

Codableେ޷͖ਓ͔ؒΒͷҰݴ • Codable͸ϦϑϨΫγϣϯʹ΋࢖͑Δ • ࠇຐज़΍ϦϑϨΫγϣϯɺίʔυੜ੒ͳͲɺ
 ࢖͑Δ΋ͷ͸͢΂ͯ࢖ָͬͯʹςετ͠Α͏