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

Realm meetup Japan #23

Realm meetup Japan #23

Realm Objective-C/Swift 2.8.3
Swift 4
- @objc/@objcMembers
- Codable (Decodable/Encodable)
- KeyPath
- Type-safe Query

9bf923e39671cde83584e3e926296c13?s=128

Kishikawa Katsumi

June 29, 2017
Tweet

Transcript

  1. Realm meetup #23 kk@realm.io

  2. #realm_jp kk@realm.io

  3. Katsumi Kishikawa Realm Inc. kk@realm.io

  4. Recent Updates • Realm Objective-C/Swift 2.8.0 (was 2.7.0) • Realm

    Java 3.4.0 (was 3.3.1) • Realm JS 1.8.2 (was 1.3.1) • Realm .NET 1.5.0 (1.4.0) kk@realm.io
  5. Realm Objective-C/Swift 2.8.3 kk@realm.io

  6. Realm Objective-C/Swift 2.8.0 • Realm Object ServerͷRealmೝূͷύεϫʔυมߋ͕ผͷϢʔβʔͰ ΋Adminݖݶ͕͋Ε͹Մೳʹ kk@realm.io

  7. Password change API kk@realm.io user.changePassword("...", forUserID: userId) { (error) in

    if let error = error { ... return } ... }
  8. Password change API (2.7.0) kk@realm.io user.changePassword("...") { (error) in if

    let error = error { ... return } ... }
  9. Realm Objective-C/Swift 2.8.2 • ϓϥΠϚϦΩʔ͕ϚΠάϨʔγϣϯͰ͖ͳ͘ͳ͍ͬͯͨ໰୊Λमਖ਼ ʢRegressionʣ • ϓϩύςΟ͕nilͷΦϒδΣΫτͰߋ৽ͨ͠ࡍͷڍಈΛҎલͱಉ͡Α ͏ʹमਖ਼ʢRegressionʣ kk@realm.io

  10. Swift 4 kk@realm.io

  11. Model Definitions kk@realm.io

  12. Limiting @objc inference SE-160 kk@realm.io

  13. Limiting @objc inference • Objective-CʹSwiftͷΠϯλʔϑΣʔεΛެ։͢ΔέʔεΛҎલΑΓ ੍ݶ͢Δ • Objective-CʹΠϯλʔϑΣʔε͕ެ։͞ΕΔέʔεΛ෼͔Γ΍͢ ͘͢Δ •

    ʢΦʔόʔϩʔυͳͲͰʣҙਤͤͣϝιου͕িಥ͢ΔͷΛ๷͙ • όΠφϦαΠζΛখ͘͞Ͱ͖Δ kk@realm.io
  14. Model Defenitions (Swift) class Person: Object { @objc dynamic var

    name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") } kk@realm.io @objcMembers class Dog: Object, Codable { dynamic var name = "" dynamic var age = 0 dynamic var owner: Person? }
  15. Model Definitions (Swift) class Person: Object { @objc dynamic var

    name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") } kk@realm.io @objcMembers class Dog: Object, Codable { dynamic var name = "" dynamic var age = 0 dynamic var owner: Person? }
  16. Codable (Decodable & Encodable) kk@realm.io

  17. Codable (Decodable & Encodable) • SwiftωΠςΟϒͷσʔλߏ଄ʢClass, Structure, EnumerationʣΛγ ϦΞϥΠζՄೳͳσʔλʹ૬ޓʹม׵͢Δ࢓૊ΈΛఏڙ͢Δ •

    DecodableʹରԠ͍ͯ͠ΔͱγϦΞϥΠζՄೳͳσʔλʢJSONͳ Ͳʣ͔ΒΦϒδΣΫτ΁ͷม׵͕ՄೳʹͳΔɻEncodable͸ͦͷٯ ʢStructure => JSONͳͲʣ • ίϯύΠϥͷࢧԉ͕ड͚ΒΕΔʢϝιου΍Ωʔͷࣗಈੜ੒ͳͲʣ ͷͰγϯϓϧʹॻ͚Δʢ৔߹͕͋Δʣ kk@realm.io
  18. Codable (Decodable & Encodable) kk@realm.io struct Person: Codable { ...

    } let person = try! JSONDecoder().decode(Person.self, from: json) let json = try! JSONEncoder().encode(person)
  19. Codable (Decodable & Encodable) kk@realm.io struct Person: Codable { private

    enum CodingKeys : String, CodingKey { case fullName = “full_name" ... } ... }
  20. Codable (Decodable & Encodable) kk@realm.io class Person: Object, Codable {

    @objc dynamic var name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") private enum CodingKeys : String, CodingKey { case name case age } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) age = try container.decode(Int.self, forKey: .age) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(age, forKey: .age) } }
  21. Codable (Decodable & Encodable) kk@realm.io @objcMembers class Dog: Object, Codable

    { dynamic var name = "" dynamic var age = 0 dynamic var owner: Person? private enum CodingKeys : String, CodingKey { case name case age case owner } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) age = try container.decode(Int.self, forKey: .age) owner = try container.decode(Person?.self, forKey: .owner) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(age, forKey: .age) try container.encode(owner, forKey: .owner) } }
  22. Decodable kk@realm.io class Person: Object, Decodable { @objc dynamic var

    name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") private enum CodingKeys : String, CodingKey { case name case age } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) age = try container.decode(Int.self, forKey: .age) } }
  23. Encodable kk@realm.io class Person: Object, Encodable { @objc dynamic var

    name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") private enum CodingKeys : String, CodingKey { case name case age } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(name, forKey: .name) try container.encode(age, forKey: .age) } }
  24. Example kk@realm.io let json = """ { "name": "Hachi", "age":

    4, "owner": { "name": "Katsumi Kishikawa", "age": 36 } } """.data(using: .utf8)! let dog = try! JSONDecoder().decode(Dog.self, from: json) print(dog) let realm = try! Realm() try! realm.write { realm.add(dog) } print(realm.objects(Dog.self))
  25. Example (GitHub API) kk@realm.io { "total_count": 410867, "incomplete_results": false, "items":

    [ { "id": 1828795, "name": "AFNetworking", "full_name": "AFNetworking/AFNetworking", "owner": { "login": "AFNetworking", "id": 1181541, "avatar_url": "https://avatars1.githubusercontent.com/u/1181541?v=3", "gravatar_id": "", "url": "https://api.github.com/users/AFNetworking", "html_url": "https://github.com/AFNetworking", "followers_url": "https://api.github.com/users/AFNetworking/followers", "following_url": "https://api.github.com/users/AFNetworking/following{/other_user}", "gists_url": "https://api.github.com/users/AFNetworking/gists{/gist_id}", "starred_url": "https://api.github.com/users/AFNetworking/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/AFNetworking/subscriptions", "organizations_url": "https://api.github.com/users/AFNetworking/orgs", "repos_url": "https://api.github.com/users/AFNetworking/repos", "events_url": "https://api.github.com/users/AFNetworking/events{/privacy}", "received_events_url": "https://api.github.com/users/AFNetworking/received_events", "type": "Organization", "site_admin": false }, ... ] } curl -i https://api.github.com/search/repositories?q=language:objc
  26. kk@realm.io @objcMembers class Repository: Object { dynamic var identifier =

    0 dynamic var name = "" dynamic var fullName = "" dynamic var isPrivate = false dynamic var htmlUrl = "" dynamic var createdAt = Date() dynamic var updatedAt = Date() dynamic var owner: User? private enum CodingKeys : String, CodingKey { case identifier = "id" case name case fullName = "full_name" case isPrivate = "private" case htmlUrl = "html_url" case createdAt = "created_at" case updatedAt = "updated_at" case owner } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) identifier = try container.decode(Int.self, forKey: .identifier) name = try container.decode(String.self, forKey: .name) fullName = try container.decode(String.self, forKey: .fullName) isPrivate = try container.decode(Bool.self, forKey: .isPrivate) htmlUrl = try container.decode(String.self, forKey: .htmlUrl) createdAt = try container.decode(Date.self, forKey: .createdAt) updatedAt = try container.decode(Date.self, forKey: .updatedAt) owner = try container.decode(User.self, forKey: .owner) } }
  27. kk@realm.io @objcMembers class User: Object, Decodable { dynamic var login

    = "" dynamic var identifier = 0 dynamic var type = "" dynamic var avatarUrl = "" private enum CodingKeys : String, CodingKey { case login case identifier = "id" case type case avatarUrl = "avatar_url" } required convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) login = try container.decode(String.self, forKey: .login) identifier = try container.decode(Int.self, forKey: .identifier) type = try container.decode(String.self, forKey: .type) avatarUrl = try container.decode(String.self, forKey: .avatarUrl) } }
  28. kk@realm.io struct RepositoryResponse : Decodable { let totalCount: Int let

    incompleteResults: Bool let items: [Repository] private enum CodingKeys : String, CodingKey { case totalCount = "total_count" case incompleteResults = "incomplete_results" case items } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) totalCount = try container.decode(Int.self, forKey: .totalCount) incompleteResults = try container.decode(Bool.self, forKey: .incompleteResults) items = try container.decode([Repository].self, forKey: .items) } }
  29. kk@realm.io var components = URLComponents(string: "https://api.github.com/search/repositories")! components.queryItems = [ URLQueryItem(name:

    "q", value: "language:objc"), URLQueryItem(name: "sort", value: "stars"), URLQueryItem(name: "order", value: "desc") ] URLSession.shared.dataTask(with: URLRequest(url: components.url!)) { data, response, error in guard let data = data else { return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let repositoryResponse = try decoder.decode(RepositoryResponse.self, from: data) let repositories = repositoryResponse.items ...
  30. KeyPath kk@realm.io

  31. KeyPath kk@realm.io @objcMembers class Dog: Object { dynamic var name

    = "" dynamic var age = 0 dynamic var owner: Person! } \Dog.age => KeyPath<Dog, Int> \Dog.name => KeyPath<Dog, String> \Dog.owner.name => KeyPath<Dog, String>
  32. Type-safe Query kk@realm.io

  33. Type-safe Query kk@realm.io extension Results { public func filter<P: Predicate>(_

    closure: () -> P) -> Results<T> where P.ObjectType == T { return filter(closure().predicate) } } public func == <RealmObject: Object>(lhs: KeyPath<RealmObject, String>, rhs: String) -> BasicPredicate<RealmObject> { return BasicPredicate<RealmObject>(format: "%K == %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as NSString]) } public func < <RealmObject: Object, RealmProperty: Numeric>(lhs: KeyPath<RealmObject, RealmProperty>, rhs: RealmProperty) -> BasicPredicate<RealmObject> { return BasicPredicate<RealmObject>(format: "%K < %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } public func > <RealmObject: Object, RealmProperty: Numeric>(lhs: KeyPath<RealmObject, RealmProperty>, rhs: RealmProperty) -> BasicPredicate<RealmObject> { return BasicPredicate<RealmObject>(format: "%K > %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } ...
  34. Type-safe Query kk@realm.io @objcMembers class Dog: Object { dynamic var

    name = "" dynamic var age = 0 dynamic var owner: Person! let owners = LinkingObjects(fromType: Person.self, property: "dogs") } @objcMembers class Person: Object { dynamic var name = "" dynamic var age = 0 dynamic var birthdate = Date() let dogs = List<Dog>() }
  35. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  36. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  37. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  38. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  39. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  40. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  41. Type-safe Query kk@realm.io realm.objects(Person.self) .filter { \Person.name == "Katsumi" }

    realm.objects(Dog.self) .filter { \Dog.age < 2 } realm.objects(Dog.self) .filter { \Dog.name < 2 } // ❌ realm.objects(Dog.self) .filter { \Person.age < 2 } // ❌ realm.objects(Dog.self) .filter { \Dog.age < 2 && \Dog.owner.name == "Katsumi" } realm.objects(Dog.self) .filter { \Dog.age < 2 && \Person.name == "Katsumi" } // ❌
  42. Proof of Concept https://github.com/kishikawakatsumi/Kuery kk@realm.io

  43. t>(lhs: KeyPath<RealmObject, String>, rhs: String) -> ect>(format: "%K == %@",

    arguments: [lhs._kvcKeyPathString! as NSString, rhs , RealmProperty: Numeric>(lhs: KeyPath<RealmObject, RealmProperty>, rhs: ealmObject> { ect>(format: "%K < %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs , RealmProperty: Numeric>(lhs: KeyPath<RealmObject, RealmProperty>, rhs: ealmObject> { ect>(format: "%K > %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs kk@realm.io
  44. Feature Request https://bugs.swift.org/browse/SR-5220 kk@realm.io

  45. Questions? Katsuma Kishikawa kk@realm.io www.realm.io/jp @k_katsumi

  46. Ξϯέʔτ bit.ly/RealmJP_23 kk@realm.io