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

WWDC 2017 Retrospective

WWDC 2017 Retrospective

Xcode 9
Swift 4
Codable
KeyPath
KVO
UIKit
AppStore
ARKit
Core ML
Core NFC
PDFKit

9bf923e39671cde83584e3e926296c13?s=128

Kishikawa Katsumi

July 14, 2017
Tweet

Transcript

  1. WWDC 2017 Retrospective kk@realm.io

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

  3. What is WWDC kk@realm.io

  4. 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
  5. Improvements/New Features kk@realm.io

  6. Xcode 9 kk@realm.io

  7. 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
  8. kk@realm.io Refactoring Swift Code

  9. kk@realm.io New source editor

  10. kk@realm.io Named Color Assets

  11. kk@realm.io Multiple concurrent simulators

  12. kk@realm.io New Build System

  13. 102 Platforms State of the Union 404 Debugging with Xcode

    9 406 Finding Bugs Using Xcode Runtime Tools kk@realm.io Reference
  14. Swift 4 kk@realm.io

  15. Swift 4 • Codable • KeyPath • Type-safe KVO •

    Generics subscript • String • Dictionary kk@realm.io
  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. 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)
  21. 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
  22. 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) } }
  23. 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) } }
  24. 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) } }
  25. 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 ...
  26. KeyPath kk@realm.io

  27. 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>
  28. 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
  29. 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 ... }
  30. Type-safe Query kk@realm.io

  31. 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]) } ...
  32. 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>() }
  33. 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" } // ❌
  34. 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" } // ❌
  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. Proof of Concept https://github.com/kishikawakatsumi/Kuery kk@realm.io

  41. 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
  42. Feature Request https://bugs.swift.org/browse/SR-5220 kk@realm.io

  43. 102 Platforms State of the Union 402 What's New in

    Swift 212 What's New in Foundation kk@realm.io Reference
  44. UIKit kk@realm.io

  45. UIKit • Drag & Drop • Browse Files • App

    Password Autofill • Larger Navigation Bar Titles • Safe Area Layout Guides kk@realm.io
  46. kk@realm.io

  47. kk@realm.io

  48. kk@realm.io

  49. kk@realm.io

  50. 102 Platforms State of the Union 201 What's New in

    Cocoa Touch 203 Introducing Drag and Drop kk@realm.io Reference
  51. AppStore kk@realm.io

  52. AppStore • New Store UI • Phased Releases • Start

    In-App Purchases From the App Store kk@realm.io
  53. kk@realm.io

  54. 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
  55. ARKit kk@realm.io

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

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

  58. Core ML kk@realm.io

  59. 703 Introducing Core ML 710 Core ML in depth 506

    Vision Framework: Building on Core ML kk@realm.io Reference
  60. Core NFC kk@realm.io

  61. kk@realm.io

  62. 718 Introducing Core NFC kk@realm.io Reference

  63. PDFKit kk@realm.io

  64. PDFKit • PDFView • PDFThumbnailView • PDFDocument • PDFPage •

    PDFOutline • PDFSelection • PDFAnnotation • PDFAction kk@realm.io
  65. 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
  66. kk@realm.io

  67. 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
  68. kk@realm.io

  69. kk@realm.io pdfView.displayMode = .singlePage pdfView.displayMode = .singlePageContinuous pdfView.displayMode = .twoUp

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

  71. kk@realm.io singlePage twoUp

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

  73. kk@realm.io PDFView with UIPageViewController

  74. 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
  75. 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..<current.numberOfChildren).reversed() { stack.append(current.child(at: i)) } } } Table of Contents (PDFOutline)
  76. 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)
  77. kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") {

    if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Draw custom contents
  78. kk@realm.io func classForPage() -> AnyClass { return WatermarkPage.self } Draw

    custom contents (e.g. Watermark)
  79. kk@realm.io

  80. 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
  81. kk@realm.io if let string = document.string { ... } Extract

    text if let string = page.string { ... }
  82. 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
  83. kk@realm.io let selections = document.findString("...", withOptions: [.caseInsensitive]) for selection in

    selections { selection.color = .yellow ... } Search text (Synchronous)
  84. kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") {

    if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Search text (Asynchronous)
  85. 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) ... }
  86. kk@realm.io Search text

  87. kk@realm.io Search text

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

    ࡧ͠·͢ɻ
  89. 241 Introducing PDFKit on iOS kk@realm.io Reference

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

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

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