Slide 1

Slide 1 text

Realm meetup #23 [email protected]

Slide 2

Slide 2 text

#realm_jp [email protected]

Slide 3

Slide 3 text

Katsumi Kishikawa Realm Inc. [email protected]

Slide 4

Slide 4 text

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) [email protected]

Slide 5

Slide 5 text

Realm Objective-C/Swift 2.8.3 [email protected]

Slide 6

Slide 6 text

Realm Objective-C/Swift 2.8.0 • Realm Object ServerͷRealmೝূͷύεϫʔυมߋ͕ผͷϢʔβʔͰ ΋Adminݖݶ͕͋Ε͹Մೳʹ [email protected]

Slide 7

Slide 7 text

Password change API [email protected] user.changePassword("...", forUserID: userId) { (error) in if let error = error { ... return } ... }

Slide 8

Slide 8 text

Password change API (2.7.0) [email protected] user.changePassword("...") { (error) in if let error = error { ... return } ... }

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Slide 11

Slide 11 text

Model Definitions [email protected]

Slide 12

Slide 12 text

Limiting @objc inference SE-160 [email protected]

Slide 13

Slide 13 text

Limiting @objc inference • Objective-CʹSwiftͷΠϯλʔϑΣʔεΛެ։͢ΔέʔεΛҎલΑΓ ੍ݶ͢Δ • Objective-CʹΠϯλʔϑΣʔε͕ެ։͞ΕΔέʔεΛ෼͔Γ΍͢ ͘͢Δ • ʢΦʔόʔϩʔυͳͲͰʣҙਤͤͣϝιου͕িಥ͢ΔͷΛ๷͙ • όΠφϦαΠζΛখ͘͞Ͱ͖Δ [email protected]

Slide 14

Slide 14 text

Model Defenitions (Swift) class Person: Object { @objc dynamic var name = "" @objc dynamic var age = 0 let dogs = LinkingObjects(fromType: Dog.self, property: "owner") } [email protected] @objcMembers class Dog: Object, Codable { dynamic var name = "" dynamic var age = 0 dynamic var owner: Person? }

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Codable (Decodable & Encodable) [email protected]

Slide 17

Slide 17 text

Codable (Decodable & Encodable) • SwiftωΠςΟϒͷσʔλߏ଄ʢClass, Structure, EnumerationʣΛγ ϦΞϥΠζՄೳͳσʔλʹ૬ޓʹม׵͢Δ࢓૊ΈΛఏڙ͢Δ • DecodableʹରԠ͍ͯ͠ΔͱγϦΞϥΠζՄೳͳσʔλʢJSONͳ Ͳʣ͔ΒΦϒδΣΫτ΁ͷม׵͕ՄೳʹͳΔɻEncodable͸ͦͷٯ ʢStructure => JSONͳͲʣ • ίϯύΠϥͷࢧԉ͕ड͚ΒΕΔʢϝιου΍Ωʔͷࣗಈੜ੒ͳͲʣ ͷͰγϯϓϧʹॻ͚Δʢ৔߹͕͋Δʣ [email protected]

Slide 18

Slide 18 text

Codable (Decodable & Encodable) [email protected] struct Person: Codable { ... } let person = try! JSONDecoder().decode(Person.self, from: json) let json = try! JSONEncoder().encode(person)

Slide 19

Slide 19 text

Codable (Decodable & Encodable) [email protected] struct Person: Codable { private enum CodingKeys : String, CodingKey { case fullName = “full_name" ... } ... }

Slide 20

Slide 20 text

Codable (Decodable & Encodable) [email protected] 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) } }

Slide 21

Slide 21 text

Codable (Decodable & Encodable) [email protected] @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) } }

Slide 22

Slide 22 text

Decodable [email protected] 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) } }

Slide 23

Slide 23 text

Encodable [email protected] 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) } }

Slide 24

Slide 24 text

Example [email protected] 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))

Slide 25

Slide 25 text

Example (GitHub API) [email protected] { "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

Slide 26

Slide 26 text

[email protected] @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) } }

Slide 27

Slide 27 text

[email protected] @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) } }

Slide 28

Slide 28 text

[email protected] 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) } }

Slide 29

Slide 29 text

[email protected] 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 ...

Slide 30

Slide 30 text

Slide 31

Slide 31 text

KeyPath [email protected] @objcMembers class Dog: Object { dynamic var name = "" dynamic var age = 0 dynamic var owner: Person! } \Dog.age => KeyPath \Dog.name => KeyPath \Dog.owner.name => KeyPath

Slide 32

Slide 32 text

Type-safe Query [email protected]

Slide 33

Slide 33 text

Type-safe Query [email protected] extension Results { public func filter(_ closure: () -> P) -> Results where P.ObjectType == T { return filter(closure().predicate) } } public func == (lhs: KeyPath, rhs: String) -> BasicPredicate { return BasicPredicate(format: "%K == %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as NSString]) } public func < (lhs: KeyPath, rhs: RealmProperty) -> BasicPredicate { return BasicPredicate(format: "%K < %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } public func > (lhs: KeyPath, rhs: RealmProperty) -> BasicPredicate { return BasicPredicate(format: "%K > %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } ...

Slide 34

Slide 34 text

Type-safe Query [email protected] @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() }

Slide 35

Slide 35 text

Type-safe Query [email protected] 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" } // ❌

Slide 36

Slide 36 text

Type-safe Query [email protected] 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" } // ❌

Slide 37

Slide 37 text

Type-safe Query [email protected] 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" } // ❌

Slide 38

Slide 38 text

Type-safe Query [email protected] 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" } // ❌

Slide 39

Slide 39 text

Type-safe Query [email protected] 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" } // ❌

Slide 40

Slide 40 text

Type-safe Query [email protected] 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" } // ❌

Slide 41

Slide 41 text

Type-safe Query [email protected] 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" } // ❌

Slide 42

Slide 42 text

Proof of Concept https://github.com/kishikawakatsumi/Kuery [email protected]

Slide 43

Slide 43 text

t>(lhs: KeyPath, rhs: String) -> ect>(format: "%K == %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs , RealmProperty: Numeric>(lhs: KeyPath, rhs: ealmObject> { ect>(format: "%K < %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs , RealmProperty: Numeric>(lhs: KeyPath, rhs: ealmObject> { ect>(format: "%K > %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs [email protected]

Slide 44

Slide 44 text

Feature Request https://bugs.swift.org/browse/SR-5220 [email protected]

Slide 45

Slide 45 text

Questions? Katsuma Kishikawa [email protected] www.realm.io/jp @k_katsumi

Slide 46

Slide 46 text

Ξϯέʔτ bit.ly/RealmJP_23 [email protected]