Slide 1

Slide 1 text

WWDC 2017 Retrospective [email protected]

Slide 2

Slide 2 text

Katsumi Kishikawa Realm Inc. [email protected]

Slide 3

Slide 3 text

What is WWDC [email protected]

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

Slide 5

Slide 5 text

Improvements/New Features [email protected]

Slide 6

Slide 6 text

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

Slide 8

Slide 8 text

[email protected] Refactoring Swift Code

Slide 9

Slide 9 text

[email protected] New source editor

Slide 10

Slide 10 text

[email protected] Named Color Assets

Slide 11

Slide 11 text

[email protected] Multiple concurrent simulators

Slide 12

Slide 12 text

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

Slide 14

Slide 14 text

Slide 15

Slide 15 text

Swift 4 • Codable • KeyPath • Type-safe KVO • Generics subscript • String • Dictionary [email protected]

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

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)

Slide 21

Slide 21 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 22

Slide 22 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 23

Slide 23 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 24

Slide 24 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 25

Slide 25 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 26

Slide 26 text

Slide 27

Slide 27 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 28

Slide 28 text

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

Slide 31

Slide 31 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 32

Slide 32 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 33

Slide 33 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 34

Slide 34 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 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

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

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

102 Platforms State of the Union 402 What's New in Swift 212 What's New in Foundation [email protected] Reference

Slide 44

Slide 44 text

Slide 45

Slide 45 text

UIKit • Drag & Drop • Browse Files • App Password Autofill • Larger Navigation Bar Titles • Safe Area Layout Guides [email protected]

Slide 46

Slide 46 text

Slide 47

Slide 47 text

Slide 48

Slide 48 text

Slide 49

Slide 49 text

Slide 50

Slide 50 text

102 Platforms State of the Union 201 What's New in Cocoa Touch 203 Introducing Drag and Drop [email protected] Reference

Slide 51

Slide 51 text

Slide 52

Slide 52 text

AppStore • New Store UI • Phased Releases • Start In-App Purchases From the App Store [email protected]

Slide 53

Slide 53 text

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

Slide 55

Slide 55 text

Slide 56

Slide 56 text

[email protected] AR Measure App Demo - Augmented reality tape measure

Slide 57

Slide 57 text

602 Introducing ARKit: Augmented Reality for iOS [email protected] Reference

Slide 58

Slide 58 text

Slide 59

Slide 59 text

703 Introducing Core ML 710 Core ML in depth 506 Vision Framework: Building on Core ML [email protected] Reference

Slide 60

Slide 60 text

Slide 61

Slide 61 text

Slide 62

Slide 62 text

718 Introducing Core NFC [email protected] Reference

Slide 63

Slide 63 text

Slide 64

Slide 64 text

PDFKit • PDFView • PDFThumbnailView • PDFDocument • PDFPage • PDFOutline • PDFSelection • PDFAnnotation • PDFAction [email protected]

Slide 65

Slide 65 text

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

Slide 67

Slide 67 text

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

Slide 69

Slide 69 text

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

[email protected] singlePageContinuous twoUpContinuous

Slide 71

Slide 71 text

[email protected] singlePage twoUp

Slide 72

Slide 72 text

[email protected] pdfView.usePageViewController(true, withViewOptions: [UIPageViewControllerOptionInterPageSpacingKey: 20]) PDFView with UIPageViewController

Slide 73

Slide 73 text

[email protected] PDFView with UIPageViewController

Slide 74

Slide 74 text

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

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

[email protected] ͸͡Ίʹ ຊॻͷ಺༰ʹ͍ͭͯ 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

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

[email protected] func classForPage() -> AnyClass { return WatermarkPage.self } Draw custom contents (e.g. Watermark)

Slide 79

Slide 79 text

Slide 80

Slide 80 text

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

[email protected] if let string = document.string { ... } Extract text if let string = page.string { ... }

Slide 82

Slide 82 text

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

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

Slide 84

Slide 84 text

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

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

[email protected] Search text

Slide 87

Slide 87 text

[email protected] Search text

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

241 Introducing PDFKit on iOS [email protected] Reference

Slide 90

Slide 90 text

241 Introducing PDFKit on iOS [email protected] Sample code (iBook clone)

Slide 91

Slide 91 text

[email protected] https://peaks.cc/iOS11

Slide 92

Slide 92 text

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