Slide 1

Slide 1 text

WWDC 2017 Retrospective kk@realm.io

Slide 2

Slide 2 text

Katsumi Kishikawa Realm Inc. kk@realm.io

Slide 3

Slide 3 text

What is WWDC kk@realm.io

Slide 4

Slide 4 text

WWDC The Apple Worldwide Developers Conference is a conference held annually in California by Apple Inc. The event is used by Apple to showcase its new software and technologies for software developers. kk@realm.io

Slide 5

Slide 5 text

Improvements/New Features kk@realm.io

Slide 6

Slide 6 text

Xcode 9 kk@realm.io

Slide 7

Slide 7 text

Xcode 9 • All new source editor • Refactoring Swift Code • New Build System • Named colors • Main Thread Checker/Undefined Behavior Sanitizer • Network debugging • Multiple concurrent simulators • Parallel device testing • Control and capture screenshots • Swift 4 kk@realm.io

Slide 8

Slide 8 text

kk@realm.io Refactoring Swift Code

Slide 9

Slide 9 text

kk@realm.io New source editor

Slide 10

Slide 10 text

kk@realm.io Named Color Assets

Slide 11

Slide 11 text

kk@realm.io Multiple concurrent simulators

Slide 12

Slide 12 text

kk@realm.io New Build System

Slide 13

Slide 13 text

102 Platforms State of the Union 404 Debugging with Xcode 9 406 Finding Bugs Using Xcode Runtime Tools kk@realm.io Reference

Slide 14

Slide 14 text

Swift 4 kk@realm.io

Slide 15

Slide 15 text

Swift 4 • Codable • KeyPath • Type-safe KVO • Generics subscript • String • Dictionary kk@realm.io

Slide 16

Slide 16 text

Codable (Decodable & Encodable) kk@realm.io

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Codable (Decodable & Encodable) kk@realm.io 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) kk@realm.io struct Person: Codable { private enum CodingKeys : String, CodingKey { case fullName = “full_name" ... } ... }

Slide 20

Slide 20 text

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)

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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) } }

Slide 23

Slide 23 text

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) } }

Slide 24

Slide 24 text

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) } }

Slide 25

Slide 25 text

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 ...

Slide 26

Slide 26 text

KeyPath kk@realm.io

Slide 27

Slide 27 text

KeyPath kk@realm.io @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 28

Slide 28 text

KeyPath kk@realm.io dog.setValue("Pochi", forKeyPath: "name") let name = dog.value(forKeyPath: "name") let name = dog[keyPath: \Dog.name] dog[keyPath: \Dog.name] = "Pochi" let age = dog[keyPath: \Dog.age] dog[keyPath: \Dog.age] = 4

Slide 29

Slide 29 text

Type-safe KVO kk@realm.io dog.addObserver(self, forKeyPath: "name", options: [], context: nil) override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "name" { ... } } dog.observe(\Dog.name) { (dog, change) in ... }

Slide 30

Slide 30 text

Type-safe Query kk@realm.io

Slide 31

Slide 31 text

Type-safe Query kk@realm.io 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 32

Slide 32 text

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() }

Slide 33

Slide 33 text

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" } // ❌

Slide 34

Slide 34 text

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" } // ❌

Slide 35

Slide 35 text

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" } // ❌

Slide 36

Slide 36 text

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" } // ❌

Slide 37

Slide 37 text

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" } // ❌

Slide 38

Slide 38 text

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" } // ❌

Slide 39

Slide 39 text

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" } // ❌

Slide 40

Slide 40 text

Proof of Concept https://github.com/kishikawakatsumi/Kuery kk@realm.io

Slide 41

Slide 41 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 kk@realm.io

Slide 42

Slide 42 text

Feature Request https://bugs.swift.org/browse/SR-5220 kk@realm.io

Slide 43

Slide 43 text

102 Platforms State of the Union 402 What's New in Swift 212 What's New in Foundation kk@realm.io Reference

Slide 44

Slide 44 text

UIKit kk@realm.io

Slide 45

Slide 45 text

UIKit • Drag & Drop • Browse Files • App Password Autofill • Larger Navigation Bar Titles • Safe Area Layout Guides kk@realm.io

Slide 46

Slide 46 text

kk@realm.io

Slide 47

Slide 47 text

kk@realm.io

Slide 48

Slide 48 text

kk@realm.io

Slide 49

Slide 49 text

kk@realm.io

Slide 50

Slide 50 text

102 Platforms State of the Union 201 What's New in Cocoa Touch 203 Introducing Drag and Drop kk@realm.io Reference

Slide 51

Slide 51 text

AppStore kk@realm.io

Slide 52

Slide 52 text

AppStore • New Store UI • Phased Releases • Start In-App Purchases From the App Store kk@realm.io

Slide 53

Slide 53 text

kk@realm.io

Slide 54

Slide 54 text

301 Introducing the New App Store 302 What's New in iTunes Connect 303 What's New in StoreKit 305 Advanced StoreKit kk@realm.io Reference

Slide 55

Slide 55 text

ARKit kk@realm.io

Slide 56

Slide 56 text

kk@realm.io AR Measure App Demo - Augmented reality tape measure

Slide 57

Slide 57 text

602 Introducing ARKit: Augmented Reality for iOS kk@realm.io Reference

Slide 58

Slide 58 text

Core ML kk@realm.io

Slide 59

Slide 59 text

703 Introducing Core ML 710 Core ML in depth 506 Vision Framework: Building on Core ML kk@realm.io Reference

Slide 60

Slide 60 text

Core NFC kk@realm.io

Slide 61

Slide 61 text

kk@realm.io

Slide 62

Slide 62 text

718 Introducing Core NFC kk@realm.io Reference

Slide 63

Slide 63 text

PDFKit kk@realm.io

Slide 64

Slide 64 text

PDFKit • PDFView • PDFThumbnailView • PDFDocument • PDFPage • PDFOutline • PDFSelection • PDFAnnotation • PDFAction kk@realm.io

Slide 65

Slide 65 text

kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") { if let document = PDFDocument(url: documentURL) { pdfView.document = document pdfView.backgroundColor = UIColor.lightGray pdfView.autoScales = true pdfView.displayMode = .singlePageContinuous pdfView.displayDirection = .vertical PDFView

Slide 66

Slide 66 text

kk@realm.io

Slide 67

Slide 67 text

kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") { if let document = PDFDocument(url: documentURL) { ... pdfThumbnailView.thumbnailSize = CGSize(width: 50, height: 75) pdfThumbnailView.layoutMode = .horizontal pdfThumbnailView.pdfView = pdfView ... PDFThumbnailView

Slide 68

Slide 68 text

kk@realm.io

Slide 69

Slide 69 text

kk@realm.io pdfView.displayMode = .singlePage pdfView.displayMode = .singlePageContinuous pdfView.displayMode = .twoUp pdfView.displayMode = .twoUpContinuous pdfView.displayDirection = .vertical pdfView.displayDirection = .horizontal PDFDisplayMode/PDFDisplayDirection

Slide 70

Slide 70 text

kk@realm.io singlePageContinuous twoUpContinuous

Slide 71

Slide 71 text

kk@realm.io singlePage twoUp

Slide 72

Slide 72 text

kk@realm.io pdfView.usePageViewController(true, withViewOptions: [UIPageViewControllerOptionInterPageSpacingKey: 20]) PDFView with UIPageViewController

Slide 73

Slide 73 text

kk@realm.io PDFView with UIPageViewController

Slide 74

Slide 74 text

kk@realm.io pdfView.goToFirstPage(nil) pdfView.goToLastPage(nil) pdfView.goToNextPage(nil) pdfView.goToPreviousPage(nil) pdfView.go(to: document.page(at: 10)!) Change current page

Slide 75

Slide 75 text

kk@realm.io if let root = pdfDocument?.outlineRoot { var stack = [root] while !stack.isEmpty { let current = stack.removeLast() if let label = current.label, !label.isEmpty { var indentationLevel = -1 var parent = current.parent while let _ = parent { indentationLevel += 1 parent = parent?.parent } print(String(repeating: " ", count: indentationLevel) + label) } for i in (0..

Slide 76

Slide 76 text

kk@realm.io ͸͡Ίʹ ຊॻͷ಺༰ʹ͍ͭͯ TechBoosterͱ͸ ͓໰͍߹Θͤઌ ୈ1ষ ReVIEWೖ໳ 1.1 ReVIEWͱ͸Կ͔ 1.2 ReVIEWͷಛ৭ 1.3 ReVIEWͷ޲͍͍ͯΔ෼໺ɺ޲͍͍ͯͳ͍෼໺ 1.4 ReVIEWͷ՝୊ 1.5 ·ͱΊ ୈ2ষ ؀ڥߏங 2.1 ReVIEW؀ڥͷߏ੒ 2.2 MacͰͷ؀ڥߏங 2.3 LinuxͰͷ؀ڥߏங 2.4 WindowsͰͷ؀ڥߏங ୈ3ষ ࣥචΛ࢝ΊΔ 3.1 ϓϩδΣΫτΛ࡞੒͢Δ Table of Contents (PDFOutline)

Slide 77

Slide 77 text

kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") { if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Draw custom contents

Slide 78

Slide 78 text

kk@realm.io func classForPage() -> AnyClass { return WatermarkPage.self } Draw custom contents (e.g. Watermark)

Slide 79

Slide 79 text

kk@realm.io

Slide 80

Slide 80 text

kk@realm.io if let document = PDFDocument(url: documentURL) { if document.isEncrypted && document.unlock(withPassword: "...") { if document.permissionsStatus == .owner { // owner... } else { // user... if document.allowsCopying { ... } if document.allowsPrinting { ... } ... } } ... Open protected PDF

Slide 81

Slide 81 text

kk@realm.io if let string = document.string { ... } Extract text if let string = page.string { ... }

Slide 82

Slide 82 text

kk@realm.io if document.pageCount > 0 { if let page = document.page(at: 0) { let text = page.string let attributedText = page.attributedString let annotations = page.annotations ... } } Extract text

Slide 83

Slide 83 text

kk@realm.io let selections = document.findString("...", withOptions: [.caseInsensitive]) for selection in selections { selection.color = .yellow ... } Search text (Synchronous)

Slide 84

Slide 84 text

kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") { if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Search text (Asynchronous)

Slide 85

Slide 85 text

kk@realm.io func documentDidBeginPageFind(_ notification: Notification) { ... } func documentDidBeginDocumentFind(_ notification: Notification) { ... } func documentDidEndDocumentFind(_ notification: Notification) { ... } func documentDidEndPageFind(_ notification: Notification) { ... } func documentDidFindMatch(_ notification: Notification) { let selection = notification.userInfo["PDFDocumentFoundSelection"] ... } func didMatchString(_ instance: PDFSelection) { instance.color = .yellow selections.append(instance) ... }

Slide 86

Slide 86 text

kk@realm.io Search text

Slide 87

Slide 87 text

kk@realm.io Search text

Slide 88

Slide 88 text

kk@realm.io Search text ߦΛ·͙ͨͳͲ͢Δͱ͍͍ͨͯ͸ۭനจࣈ͕ؒʹೖͬͯ͠·ͬͯʮΠϯ ε τʔϧʯͷΑ͏ʹͳΔͷͰݕࡧʹϚον͠ͳ͍͜ͱ͕͋Γ·͢ɻ PDFʹ͸ߦ΍ஈམͱ͍͏֓೦͸ͳ͍ͷͰ͜Ε͸࢓ํ͕ͳ͍͜ͱͰ͢ɻ iBooks΍DropBoxΞϓϦͰಉ༷ͷ݁ՌʹͳΓ·͢ɻ ͜ͷ໰୊Λආ͚Δʹ͸ςΩετΛऔΓग़͠ɺۭന΍ۭߦΛऔΓআ͘ͳͲͯ͠ ΫϦʔχϯάࡁΈͷςΩετΛผ్σʔλϕʔεͳͲʹอ࣋͠ɺ͔ͦ͜Βݕ ࡧ͠·͢ɻ

Slide 89

Slide 89 text

241 Introducing PDFKit on iOS kk@realm.io Reference

Slide 90

Slide 90 text

241 Introducing PDFKit on iOS kk@realm.io Sample code (iBook clone)

Slide 91

Slide 91 text

kk@realm.io https://peaks.cc/iOS11

Slide 92

Slide 92 text

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