Slide 1

Slide 1 text

KeyPathͷศརͳ࢖͍ಓΛ ݚڀ͢Δ Otemachi.swift x Kyobashi.swift #01 Katsumi Kishikawa [email protected]

Slide 2

Slide 2 text

Katsumi Kishikawa Realm Inc. [email protected]

Slide 3

Slide 3 text

KeyPathͱ͸ [email protected]

Slide 4

Slide 4 text

KeyPath in Swift 3 [email protected] #keyPath(Book.title) #keyPath(Book.price) #keyPath(Book.author.name) @objcMembers class Book { var isbn: String var title: String var price: Int var author: Author ... }

Slide 5

Slide 5 text

KeyPath in Swift 3 [email protected] let titleKeyPath: String = #keyPath(Book.title) let priceKeyPath: String = #keyPath(Book.price) let authorNameKeyPath: String = #keyPath(Book.author.name)

Slide 6

Slide 6 text

KeyPath in Swift 3 [email protected] let title: Any? = book.value(forKeyPath: #keyPath(Book.title)) let price: Any? = book.value(forKeyPath: #keyPath(Book.price)) let authorName: Any? = book.value(forKeyPath: #keyPath(Book.author.name))

Slide 7

Slide 7 text

Smart KeyPath in Swift 4 [email protected] struct Book { let isbn: String let title: String let price: Int let author: Author } struct Author { let name: String let age: Int? let birthday: Date? }

Slide 8

Slide 8 text

Smart KeyPath in Swift 4 [email protected] struct Book { let isbn: String let title: String let price: Int let author: Author } struct Author { let name: String let age: Int? let birthday: Date? } let book = Book(isbn: "978-4774188485", title: "Realmೖ໳", price: 3110, author: Author(name: "ੁݪ ༞", age: nil, birthday: nil))

Slide 9

Slide 9 text

Smart KeyPath in Swift 4 [email protected] let book = Book(isbn: "978-4774188485", title: "Realmೖ໳", price: 3110, author: Author(name: "ੁݪ ༞", age: nil, birthday: nil)) let title = book[keyPath: \Book.title] // => "Realmೖ໳" let price = book[keyPath: \Book.price] // => 3110 let authorName = book[keyPath: \Book.author.name] // "ੁݪ ༞"

Slide 10

Slide 10 text

Smart KeyPath in Swift 4 [email protected] let title = book[keyPath: \Book.title] // => "Realmೖ໳" let price = book[keyPath: \Book.price] // => 3110 let authorName = book[keyPath: \Book.author.name] // "ੁݪ ༞" \Book.title \Book.price \Book.author.name

Slide 11

Slide 11 text

[email protected] let titleKeyPath: KeyPath = \Book.title let priceKeyPath: KeyPath = \Book.price let authorNameKeyPath: KeyPath = \Book.author.name Smart KeyPath in Swift 4

Slide 12

Slide 12 text

[email protected] let title: String = book[keyPath: \Book.title] let price: Int = book[keyPath: \Book.price] let authorName: String = book[keyPath: \Book.author.name] Smart KeyPath in Swift 4

Slide 13

Slide 13 text

ϓϩύςΟΞΫηε [email protected] let title = book.title // => "Realmೖ໳" let price = book.price // => 3110 let authorName = book.author.name // "ੁݪ ༞" ΄ͱΜͲͷέʔε͸ϓϩύςΟΞΫηεͷํ͕؆୯͔ͭద͍ͯ͠ΔͷͰɺ KVOͳͲݹ͍࢓૊Έͷޓ׵ੑҎ֎ͷ໨తͰΘ͟Θ͟KeyPathΛ࢖͏৔໘͸গͳ͍ ̍ͭ͸NSPredicateͳͲϓϩύςΟͷ৘ใΛจࣈྻͰ૊Έཱ͍ͯͯΔΑ͏ͳ APIΛ҆શʹॻ͚ΔΑ͏ʹ͢Δ͜ͱ͕Ͱ͖ͦ͏ɻ ଞʹศརͳར༻ํ๏͸͋ΔͩΖ͏͔ʁ

Slide 14

Slide 14 text

Type-safe KVO [email protected]

Slide 15

Slide 15 text

Type-unsafe KVO in Swift 3 [email protected] book.addObserver(self, forKeyPath: #keyPath(Book.price), options: [], context: nil) override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "price" { if let book = object as? Book { if book.price < 2000 { } } } }

Slide 16

Slide 16 text

Type-safe KVO [email protected] book.observe(\Book.price) { (observed, change) in if (observed.price < 2000) { } } KVOΛੵۃతʹ࢖͏͜ͱ͸ͳ͍͚ΕͲɺiOSϓϩάϥϛϯάͰ͸·ͩඞཁʹͳΔɻ ࠓճͷվળʹΑΓΩϟετ͕ෆཁʹͳΔͳͲɺSwiit͔Βඇৗʹѻ͍΍͘͢ͳͬͨɻ

Slide 17

Slide 17 text

Type-safe Query (Experimental) [email protected]

Slide 18

Slide 18 text

Type-unsafe Query in Swift 4 [email protected] NSPredicate(format: "name == %@", "Katsumi") NSPredicate(format: "age > %@", 20) NSPredicate(format: "nsme == %@", "Katsumi") // Runtime Error NSPredicate(format: "name > %@", 20) // Runtime Error NSPredicateͳͲϓϩύςΟͷ৘ใΛจࣈྻͰࢦఆ͢Δඞཁ͕͋ΔAPIΛ KeyPathΛ࢖ͬͯΑΓ҆શʹॻ͚Δ

Slide 19

Slide 19 text

Type-safe Query [email protected] Query(Person.self).filter(\Person.name == "Katsumi") Query(Person.self).filter(\Person.age > 20) Query(Person.self).filter(\Person.name > 20) // Compile error Query(Person.self).filter(\Dog.name == "John") // Compile error KeyPath͸ίϯύΠϥͷνΣοΫ͕ಇ͘ͷͰɺTypo͕ͳ͘ͳΔʢίʔυิ׬΋Մೳʣ ͜ͱ͸΋ͪΖΜɺܕνΣοΫ΋ػೳ͢Δɻ

Slide 20

Slide 20 text

Type-safe Query [email protected] ... public func == (lhs: KeyPath, rhs: String) -> BasicPredicate { return BasicPredicate(format: "%K == %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as NSString]) } public func != (lhs: KeyPath, rhs: String) -> BasicPredicate { return BasicPredicate(format: "%K != %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as NSString]) } public func < (lhs: KeyPath, rhs: Property) -> BasicPredicate { return BasicPredicate(format: "%K < %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } public func > (lhs: KeyPath, rhs: Property) -> BasicPredicate { return BasicPredicate(format: "%K > %@", arguments: [lhs._kvcKeyPathString! as NSString, rhs as! NSNumber]) } ...

Slide 21

Slide 21 text

Type-safe Query (for Realm) [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 22

Slide 22 text

Type-safe Query (for Realm) [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 } // ❌

Slide 23

Slide 23 text

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

Slide 24

Slide 24 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] ݱࡏKeyPathͷจࣈྻදݱ͸ඇެ։ͳͷͰ࢖͑ΔΑ͏ʹͯ͠΄͍͠ͱ͍͏ϦΫΤετΛ ग़͍ͯ͠Δ

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Slide 27

Slide 27 text

Lenses in Swift [email protected]

Slide 28

Slide 28 text

Lensͱ͸ [email protected] • ؔ਺ܕϓϩάϥϛϯάʹ͓͍ͯGetter/SetterΛந৅Խ͢ΔσβΠϯύλʔϯ • ෆมੑΛอͬͨ··ෳࡶͳσʔλߏ଄΁ͷ༰қͳΞΫηεΛఏڙ͢Δ

Slide 29

Slide 29 text

ΦϒδΣΫτࢦ޲ݴޠͳΒ... [email protected] let title = book.title let price = book.price let authorName = book.author.name book.author.name = "..." Lens͸ͦ΋ͦ΋ؔ਺ܕϓϩάϥϛϯάʹ͓͍ͯΦϒδΣΫτࢦ޲ݴޠͷػೳΛ ΤϛϡϨʔτ͢Δ΋ͷͳͷͰɺSwiftʹಋೖͯ͠ศརͳ఺͸͋ΔͷͩΖ͏͔ʁ

Slide 30

Slide 30 text

Lenses in Swift [email protected] struct BoardingPass { let plane: Plane let gate: Gate let departureDate: Date let arrivalDate: Date } struct Plane { let model: String let freeSeats: Int let takenSeats: Int let status: Status } struct Gate { let number: Int let letter: String } enum Status { case early case onTime case late } // https://github.com/typelift/Focus

Slide 31

Slide 31 text

Lenses in Swift [email protected] let plane = Plane(model: "SpaceX Raptor", freeSeats: 4, takenSeats: 0, status: .onTime) let gate = Gate(number: 1, letter: "A") let pass = BoardingPass(plane: plane, gate: gate, departureDate: Date.distantFuture, arrivalDate: Date.distantFuture) // https://github.com/typelift/Focus

Slide 32

Slide 32 text

Lenses in Swift [email protected] let newPlane = Plane(model: pass.plane.model, freeSeats: pass.plane.freeSeats, takenSeats: pass.plane.takenSeats, status: .late) let newPass = BoardingPass(plane: newPlane, gate: pass.gate, departureDate: pass.departureDate, arrivalDate: pass.arrivalDate) ▿ BoardingPass ▿ plane: Plane - model: "SpaceX Raptor" - freeSeats: 4 - takenSeats: 0 - status: Status.late ▿ gate: Gate - number: 1 - letter: "A" - departureDate: 4001-01-01 00:00:00 +0000 - arrivalDate: 4001-01-01 00:00:00 +0000

Slide 33

Slide 33 text

Lenses in Swift [email protected] var pass = BoardingPass(plane: plane, gate: gate, departureDate: Date.distantFuture, arrivalDate: Date.distantFuture) pass.plane.status = .late

Slide 34

Slide 34 text

Lenses in Swift [email protected] let newPass = pass |> (\BoardingPass.plane.status) .~ .late Lens͸ͦ΋ͦ΋ؔ਺ܕϓϩάϥϛϯάʹ͓͍ͯΦϒδΣΫτࢦ޲ݴޠͷػೳΛ ΤϛϡϨʔτ͢Δ΋ͷͳͷͰɺSwiftʹಋೖͯ͠ศརͳ఺͸͋ΔͷͩΖ͏͔ʁ

Slide 35

Slide 35 text

Lenses in Swift [email protected] let newPass = pass |> (\BoardingPass.plane.status) .~ .late ▿ BoardingPass ▿ plane: Plane - model: "SpaceX Raptor" - freeSeats: 4 - takenSeats: 0 - status: Status.late ▿ gate: Gate - number: 1 - letter: "A" - departureDate: 4001-01-01 00:00:00 +0000 - arrivalDate: 4001-01-01 00:00:00 +0000

Slide 36

Slide 36 text

LensͰςετΛΘ͔Γ΍͘͢ [email protected] func testUser() { let templateUser = User(name: "Katsumi Kishikawa", age: 37, sns: SNS(twitter: "k_katsumi", facebook: nil)) XCTContext.runActivity(named: "User is student") { (activity) in let studentUser = User(name: templateUser.name, age: 37, sns: templateUser.sns) ... XCTAssert(...) } XCTContext.runActivity(named: "...") { (activity) in let silverUser = User(name: templateUser.name, age: 65, sns: templateUser.sns) ... XCTAssert(...) } }

Slide 37

Slide 37 text

LensͰςετΛΘ͔Γ΍͘͢ [email protected] func testUser() { let templateUser = User(name: "Katsumi Kishikawa", age: 37, sns: SNS(twitter: "k_katsumi", facebook: nil)) XCTContext.runActivity(named: "User is student") { (activity) in let studentUser = user |> (\User.age) .~ 15 ... XCTAssert(...) } XCTContext.runActivity(named: "...") { (activity) in let silverUser = user |> (\User.age) .~ 65 ... XCTAssert(...) } } LensʹΑͬͯಛఆͷϓϩύςΟ͚ͩΛมߋͰ͖ΔͷͰ ೥ྸʹϑΥʔΧε͍ͯ͠Δ͜ͱ͕ΑΓΘ͔Γ΍͍͢ʁ

Slide 38

Slide 38 text

LensͰςετΛΘ͔Γ΍͘͢ [email protected] Quick + LensΛ༻͍ͨςετͷߏ଄Խ https://speakerdeck.com/nonchalant/quick-plus-lenswoyong-itatesutofalsegou-zao-hua kickstarter/ios-oss https://github.com/kickstarter/ios-oss/blob/3e112451a306c27319704e5a67d4edea7ed66014/Kickstarter-iOS/Views/Controllers/ActivitiesViewControllerTests.swift#L9-L15

Slide 39

Slide 39 text

Questions? Katsumi Kishikawa [email protected] www.realm.io @k_katsumi [email protected]