Slide 1

Slide 1 text

Swift 4 Codable 2017/06/19 CA.swift #3 WWDC17ใࠂձ Yasuhiro Inami / @inamiy

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Session 212 What's New in Foundation https://developer.apple.com/videos/play/ wwdc2017/212/

Slide 7

Slide 7 text

Swift 4 Codable* Conversion between Swift data structure and archived formats * swift-DEVELOPMENT-SNAPSHOT-2017-06-17-a (Xcode 9 Beta 1ʙ) ݱࡏ

Slide 8

Slide 8 text

[SE-0166] Swift Archival & Serialization [SE-0167] Swift Encoders

Slide 9

Slide 9 text

Codable ͷྫ // JSON { "int": 0, "float": 1.5, "double": 3.25, "string": "Swift", "array": [ "Hello", "World" ], "dict": { "WWDC": 2017 }, "date": 1496682000, }

Slide 10

Slide 10 text

! [SE-0168] Multi-Line String Literals let json = """ { "int": 0, "float": 1.5, "double": 3.25, "string": "Swift", "array": [ "Hello", "World" ], "dict": { "WWDC": 2017 }, "date": 1496682000, } """.data(using: .utf8)!

Slide 11

Slide 11 text

struct MyData: Codable { let int: Int let float: Float let double: Double let string: String let array: [String] let dict: [String: Int] let date: Date } protocol Codable Λ࠾༻͢Δ͚ͩͰɾɾɾ

Slide 12

Slide 12 text

let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 let decoded = try decoder.decode(MyData.self, from: json) // MyData(int: 0, float: 1.5, double: 3.25, string: "Swift", // array: ["Hello", "World"], dict: ["WWDC": 2017], // date: 2017-06-05 17:00:00 +0000) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .secondsSince1970 let encodedData = try encoder.encode(decoded) let encodedString = String(data: encodedData, encoding: .utf8)! // {"double":3.25,"int":0,"string":"Swift","date":1496682000, // "array":["Hello","World"],"float":1.5,"dict":{"WWDC":2017}} σίʔυɾΤϯίʔυॲཧ͕؆୯ʂ

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

ϏϧτΠϯͷ Decoder/Encoder • JSON(De|En)coder ... JSONSerializationΛ࢖༻ • date(De|En)codingStrategy • data(De|En)codingStrategy • nonConformingFloat(De|En)codingStrategy • PropertyList(De|En)coder ... PropertyListSerializationΛ ࢖༻

Slide 15

Slide 15 text

ϏϧτΠϯͷ Codableܕ • Swift ͷجຊσʔλܕ • Bool, Int, Int(N), UInt(N), Float, Double, String, RawRepresentable, Optional, Array, Set, Dictionary • Foundation / CoreGraphics ͷσʔλܕ • AffineTransform, Calendar, CharacterSet, Data, Date, DateComponents, DateInterval, Decimal, IndexPath, IndexSet, Locale, Measurement, NSRange, PersonNameComponents, TimeZone, URL, UUID, CGFloat

Slide 16

Slide 16 text

Q. Ͳ͏΍ͬͯຐ๏Λ࣮ݱ ͍ͯ͠Δ!ʁ

Slide 17

Slide 17 text

A. ίϯύΠϥʹΑΔ ࣗಈੜ੒

Slide 18

Slide 18 text

struct User: Codable { var userName: String var score: Int @derived private enum CodingKeys: String, CodingKey { // @derived = ࣗಈੜ੒ case userName case score } @derived init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) userName = try container.decode(String.self, forKey: .userName) score = try container.decode(Int.self, forKey: .score) } @derived func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(userName, forKey: .userName) try container.encode(score, forKey: .score) } }

Slide 19

Slide 19 text

struct User: Codable { var userName: String // JSON ͸ "user_name"ΩʔΛ͍࣋ͬͯΔ var score: Int // 0...100 ͷΈʹݶఆ͍ͨ͠ private enum CodingKeys: String, CodingKey { // declare explicitly case userName = "user_name" case score } init(from decoder: Decoder) throws { // declare explicitly let container = try decoder.container(keyedBy: CodingKeys.self) score = try container.decode(Int.self, forKey: .score) guard (0...100).contains(score) else { // validation throw DecodingError.dataCorrupted( codingPath: container.codingPath + [CodingKeys.score], debugDescription: "score is not in range 0...100" ) } userName = try container.decode(String.self, forKey: .userName) } }

Slide 20

Slide 20 text

Codable ϓϩτίϧ ˍ ܕͷશମ૾

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

public typealias Codable = Encodable & Decodable public protocol Decodable { init(from decoder: Decoder) throws } public protocol Encodable { func encode(to encoder: Encoder) throws } public protocol CodingKey { public var stringValue: String { get } public init?(stringValue: String) public var intValue: Int? { get } public init?(intValue: Int) }

Slide 23

Slide 23 text

public protocol Decoder { var codingPath: [CodingKey?] { get } var userInfo: [CodingUserInfoKey : Any] { get } func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer func unkeyedContainer() throws -> UnkeyedDecodingContainer func singleValueContainer() throws -> SingleValueDecodingContainer } public protocol Encoder { var codingPath: [CodingKey?] { get } var userInfo: [CodingUserInfoKey : Any] { get } func container(keyedBy type: Key.Type) -> KeyedEncodingContainer func unkeyedContainer() -> UnkeyedEncodingContainer func singleValueContainer() -> SingleValueEncodingContainer }

Slide 24

Slide 24 text

public protocol KeyedDecodingContainerProtocol { associatedtype Key : CodingKey var codingPath: [CodingKey?] { get } var allKeys: [Key] { get } func contains(_ key: Key) -> Bool func decode(_ type: T.Type, forKey key: Key) throws -> T func decodeIfPresent (_ type: T.Type, forKey key: Key) throws -> T? func nestedContainer (keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer func superDecoder() throws -> Decoder func superDecoder(forKey key: Key) throws -> Decoder ... }

Slide 25

Slide 25 text

public protocol KeyedEncodingContainerProtocol { associatedtype Key : CodingKey var codingPath: [CodingKey?] { get } mutating func encode(_ value: T, forKey key: Key) throws mutating func encodeWeak (_ object: T, forKey key: Key) throws mutating func encodeIfPresent (_ value: T?, forKey key: Key) throws mutating func nestedContainer (keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder mutating func superEncoder(forKey key: Key) -> Encoder ... }

Slide 26

Slide 26 text

public protocol UnkeyedDecodingContainer { var codingPath: [CodingKey?] { get } var count: Int? { get } var isAtEnd: Bool { get } mutating func decode(_ type: T.Type) throws -> T mutating func decodeIfPresent (_ type: T.Type) throws -> T? mutating func nestedContainer (keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer mutating func superDecoder() throws -> Decoder ... }

Slide 27

Slide 27 text

public protocol UnkeyedEncodingContainer { var codingPath: [CodingKey?] { get } mutating func encode(_ value: T) throws mutating func encodeWeak (_ object: T) throws mutating func encode(contentsOf sequence: T) throws where T.Iterator.Element : Encodable mutating func nestedContainer (keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder ... }

Slide 28

Slide 28 text

public protocol SingleValueDecodingContainer { public func decodeNil() -> Bool public func decode(_ type: T.Type) throws -> T where T : Decodable ... } public protocol SingleValueEncodingContainer { public mutating func encodeNil() throws public mutating func encode(_ value: T) throws where T : Encodable ... }

Slide 29

Slide 29 text

public enum DecodingError : Error { public struct Context { public let codingPath: [CodingKey?] public let debugDescription: String } case typeMismatch(Any.Type, DecodingError.Context) case valueNotFound(Any.Type, DecodingError.Context) case keyNotFound(CodingKey, DecodingError.Context) case dataCorrupted(DecodingError.Context) } public enum EncodingError : Error { public struct Context { public let codingPath: [CodingKey?] public let debugDescription: String } case invalidValue(Any, EncodingError.Context) } public struct CodingUserInfoKey : RawRepresentable, Equatable, Hashable { public typealias RawValue = String }

Slide 30

Slide 30 text

ϓϩτίϧ ˍ ܕͷશମ૾ (1) • CodingKey • ܕ҆શͳStringΩʔʢIntΩʔ΋Մʣ • superDecoder/superEncoder • εʔύʔΫϥεʹ౉͢༻ʢͪΌΜͱಈ͍ͯΔʁʣ • Τϥʔॲཧ (DecodingError/EncodingError) • codingPathΛه࿥ͯ͠ɺΤϥʔՕॴͷৄࡉΛग़ྗ

Slide 31

Slide 31 text

ϓϩτίϧ ˍ ܕͷશମ૾ (2) • (Keyed|Unkeyed|SingleValue)(De|En)codingContainer • (De|En)coder ͕௚઀ (de|en)code ॲཧ͢Δ୅ΘΓʹɺ֤ Container͕୲౰ • (JSONSerialization౳ʹΑΔ) தؒදݱ͕ [String: Any] ౳ͷͨΊɺunbox/box-ingͰܕ҆શʹॲཧ͢ΔͨΊʹ Container͕ඞཁ • [SE-0143] ConditionalConformancesʹΑΔվળͷ༨஍

Slide 32

Slide 32 text

Q. ίϯύΠϥ͸ Ͳ͏΍ͬͯ ॲཧ͍ͯ͠Δʁ!

Slide 33

Slide 33 text

// include/swift/AST/KnownProtocols.(h|def) enum class KnownProtocolKind : uint8_t { ... CodingKey, Encodable, Decodable, }; // include/swift/AST/(ASTContext.h|KnownIdentifiers.def) class ASTContext { ... Identifier Id_CodingKeys, Identifier Id_Decodable, Identifier Id_decode, Identifier Id_decodeIfPresent, Identifier Id_Decoder, Identifier Id_decoder, Identifier Id_superDecoder, }

Slide 34

Slide 34 text

static EnumDecl *synthesizeCodingKeysEnum(TypeChecker &tc, NominalTypeDecl *target, ProtocolDecl *proto) { auto &C = tc.Context; auto *codingKeyProto = C.getProtocol(KnownProtocolKind::CodingKey); auto *codingKeyType = codingKeyProto->getDeclaredType(); TypeLoc protoTypeLoc[1] = {TypeLoc::withoutLoc(codingKeyType)}; MutableArrayRef inherited = C.AllocateCopy(protoTypeLoc); auto *enumDecl = new (C) EnumDecl(SourceLoc(), C.Id_CodingKeys, SourceLoc(), inherited, nullptr, target); enumDecl->setImplicit(); enumDecl->setAccessibility(Accessibility::Private); // `private enum CodingKeys: CodingKey` Λੜ੒ ... return enumDecl }

Slide 35

Slide 35 text

// lib/Sema/DerivedConformanceCodable.cpp // lib/Sema/DerivedConformanceCodingKey.cpp /// `init(from decoder: Decoder) throws`͕ແ͍৔߹ɺࣗಈੜ੒ static void deriveBodyDecodable_init(...) /// `func encode(to encoder: Encoder) throws`͕ແ͍৔߹ɺࣗಈੜ੒ static void deriveBodyEncodable_encode(...) /// `private enum CodingKeys: CodingKey`͕ແ͍৔߹ɺҰ͔Β࡞Δ static EnumDecl *synthesizeCodingKeysEnum(...) /// `enum CodingKey: CodingKey`͕͋Δ৔߹ɺ /// `var stringValue: String`౳Λࣗಈੜ੒ ValueDecl *DerivedConformance::deriveCodingKey(...)

Slide 36

Slide 36 text

CodableͰؾʹͳͬͨ఺

Slide 37

Slide 37 text

ܕม਺͕ඞཁ let myData: MyData let decoder = JSONDecoder() myData = try decoder.decode(MyData.self, from: json) ฦΓ஋Λܕਪ࿦ͤͨ͞ํ͕γϯϓϧˍԠ༻͕ޮ͘ͷͰ͸ʁ myData = try decoder.decode(from: json)

Slide 38

Slide 38 text

https://twitter.com/dgregor79/status/873960672060514304

Slide 39

Slide 39 text

static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl, NominalTypeDecl *target, ProtocolDecl *proto) { llvm::SmallDenseMap properties; for (auto *varDecl : target->getStoredProperties(/*skipInaccessible=*/true)) { properties[varDecl->getName()] = varDecl; } // Codable ͷ stored properties ͱ CodingKey ͷ cases Λൺֱͯ͠ɺ1ର1ରԠ͔Ͳ͏͔ΛνΣοΫ bool propertiesAreValid = true; for (auto elt : codingKeysDecl->getAllElements()) { auto it = properties.find(elt->getName()); if (it == properties.end()) { tc.diagnose(elt->getLoc(), diag::codable_extraneous_codingkey_case_here, elt->getName()); propertiesAreValid = false; continue; } ... } if (!propertiesAreValid) return false; ... }

Slide 40

Slide 40 text

Q. Codable ͷ Stored properties ͱ CodingKey cases ͕ 1ର1ରԠ͠ͳ͍৔߹͸ʁ! (ྫɿMyCodableͷ͋ΔϓϩύςΟΛ"ෳ਺ͷΩʔͷ஋"͔Βࢉग़͍ͨ͠ɺͳͲ)

Slide 41

Slide 41 text

ʢ஌͍ͬͯΔਓ͍ͨΒڭ͍͑ͯͩ͘͞ʣ !

Slide 42

Slide 42 text

·ͱΊ • Codable ͸Appleެࣜͷຐ๏ • 3೥ʹ౉ΔSwift JSONϥΠϒϥϦઓ૪ʹऴࢭූ • ͱ͸͍͑ɺContainer + CodingKey ͷखಈ࣮૷͸໘౗ • ॴײ • Container͸ɺܕ҆શͰͳ͍چ࣌୅ͷͨΊͷworkaround • CodingKeyͷ1:1ରԠ͸ɺएׯ΍Γ͗͢ʁ

Slide 43

Slide 43 text

ͱ͸͍͑ɺAPI͸៉ྷͩ͠ɺ CodableʹରԠ͢Ε͹ ৭ʑͳ(De|En)coderʹ ରԠͰ͖Δ!

Slide 44

Slide 44 text

Thanks! Yasuhiro Inami @inamiy