Share
Embed
Swift 4 Codable
CA.swift #3 WWDC17 報告会 (Jun 19, 2017)
https://cyberagent.connpass.com/event/58796/
--------------------------------------------------------------------------------
2017/06/21 Updated:
Q. `Codable` の Stored properties と`CodingKey` cases が1対1対応しない場合は?🤔
(例:MyCodableのあるプロパティを"複数のキーの値"から算出したい、など)
A. `enum CodingKeys`, `init`, `func encode`すべて実装すればOK
(参考: https://twitter.com/kitasuke/status/877404773900722176)
Yasuhiro Inami
June 19, 2017
Transcript
44 slides
-
Swift 4 Codable* Conversion between Swift data structure and archived
formats * swift-DEVELOPMENT-SNAPSHOT-2017-06-17-a (Xcode 9 Beta 1ʙ) ݱࡏ -
Codable ͷྫ // JSON { "int": 0, "float": 1.5, "double":
3.25, "string": "Swift", "array": [ "Hello", "World" ], "dict": { "WWDC": 2017 }, "date": 1496682000, } -
! [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)! -
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 Λ࠾༻͢Δ͚ͩͰɾɾɾ -
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}} σίʔυɾΤϯίʔυॲཧ͕؆୯ʂ -
ϏϧτΠϯͷ Decoder/Encoder • JSON(De|En)coder ... JSONSerializationΛ༻ • date(De|En)codingStrategy • data(De|En)codingStrategy
• nonConformingFloat(De|En)codingStrategy • PropertyList(De|En)coder ... PropertyListSerializationΛ ༻ -
ϏϧτΠϯͷ 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 -
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) } } -
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) } } -
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) } -
public protocol Decoder { var codingPath: [CodingKey?] { get }
var userInfo: [CodingUserInfoKey : Any] { get } func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> func unkeyedContainer() throws -> UnkeyedDecodingContainer func singleValueContainer() throws -> SingleValueDecodingContainer } public protocol Encoder { var codingPath: [CodingKey?] { get } var userInfo: [CodingUserInfoKey : Any] { get } func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> func unkeyedContainer() -> UnkeyedEncodingContainer func singleValueContainer() -> SingleValueEncodingContainer } -
public protocol KeyedDecodingContainerProtocol { associatedtype Key : CodingKey var codingPath:
[CodingKey?] { get } var allKeys: [Key] { get } func contains(_ key: Key) -> Bool func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T func decodeIfPresent<T : Decodable> (_ type: T.Type, forKey key: Key) throws -> T? func nestedContainer<NestedKey> (keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer func superDecoder() throws -> Decoder func superDecoder(forKey key: Key) throws -> Decoder ... } -
public protocol KeyedEncodingContainerProtocol { associatedtype Key : CodingKey var codingPath:
[CodingKey?] { get } mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws mutating func encodeWeak<T : AnyObject & Encodable> (_ object: T, forKey key: Key) throws mutating func encodeIfPresent<T : Encodable> (_ value: T?, forKey key: Key) throws mutating func nestedContainer<NestedKey> (keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder mutating func superEncoder(forKey key: Key) -> Encoder ... } -
public protocol UnkeyedDecodingContainer { var codingPath: [CodingKey?] { get }
var count: Int? { get } var isAtEnd: Bool { get } mutating func decode<T : Decodable>(_ type: T.Type) throws -> T mutating func decodeIfPresent<T : Decodable> (_ type: T.Type) throws -> T? mutating func nestedContainer<NestedKey> (keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer mutating func superDecoder() throws -> Decoder ... } -
public protocol UnkeyedEncodingContainer { var codingPath: [CodingKey?] { get }
mutating func encode<T : Encodable>(_ value: T) throws mutating func encodeWeak<T : AnyObject & Encodable> (_ object: T) throws mutating func encode<T : Sequence>(contentsOf sequence: T) throws where T.Iterator.Element : Encodable mutating func nestedContainer<NestedKey> (keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer mutating func superEncoder() -> Encoder ... } -
public protocol SingleValueDecodingContainer { public func decodeNil() -> Bool public
func decode<T>(_ type: T.Type) throws -> T where T : Decodable ... } public protocol SingleValueEncodingContainer { public mutating func encodeNil() throws public mutating func encode<T>(_ value: T) throws where T : Encodable ... } -
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 } -
ϓϩτίϧ ˍ ܕͷશମ૾ (1) • CodingKey • ܕ҆શͳStringΩʔʢIntΩʔՄʣ • superDecoder/superEncoder
• εʔύʔΫϥεʹ͢༻ʢͪΌΜͱಈ͍ͯΔʁʣ • Τϥʔॲཧ (DecodingError/EncodingError) • codingPathΛهͯ͠ɺΤϥʔՕॴͷৄࡉΛग़ྗ -
ϓϩτίϧ ˍ ܕͷશମ૾ (2) • (Keyed|Unkeyed|SingleValue)(De|En)codingContainer • (De|En)coder ͕ (de|en)code
ॲཧ͢ΔΘΓʹɺ֤ Container͕୲ • (JSONSerializationʹΑΔ) தؒදݱ͕ [String: Any] ͷͨΊɺunbox/box-ingͰܕ҆શʹॲཧ͢ΔͨΊʹ Container͕ඞཁ • [SE-0143] ConditionalConformancesʹΑΔվળͷ༨ -
// 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, } -
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<TypeLoc> 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 } -
// 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(...) -
ܕม͕ඞཁ let myData: MyData let decoder = JSONDecoder() myData =
try decoder.decode(MyData.self, from: json) ฦΓΛܕਪͤͨ͞ํ͕γϯϓϧˍԠ༻͕ޮ͘ͷͰʁ myData = try decoder.decode(from: json) -
static bool validateCodingKeysEnum(TypeChecker &tc, EnumDecl *codingKeysDecl, NominalTypeDecl *target, ProtocolDecl *proto)
{ llvm::SmallDenseMap<Identifier, VarDecl *, 8> 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; ... } -
Q. Codable ͷ Stored properties ͱ CodingKey cases ͕ 1ର1ରԠ͠ͳ͍߹ʁ!
(ྫɿMyCodableͷ͋ΔϓϩύςΟΛ"ෳͷΩʔͷ"͔Βࢉग़͍ͨ͠ɺͳͲ) -
·ͱΊ • Codable Appleެࣜͷຐ๏ • 3ʹΔSwift JSONϥΠϒϥϦઓ૪ʹऴࢭූ • ͱ͍͑ɺContainer +
CodingKey ͷखಈ࣮໘ • ॴײ • Containerɺܕ҆શͰͳ͍چ࣌ͷͨΊͷworkaround • CodingKeyͷ1:1ରԠɺएׯΓ͗͢ʁ