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

Kishikawa Katsumi

July 14, 2017
Tweet

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 102 Platforms State of the Union 404 Debugging with Xcode

    9 406 Finding Bugs Using Xcode Runtime Tools kk@realm.io Reference
  4. Swift 4 • Codable • KeyPath • Type-safe KVO •

    Generics subscript • String • Dictionary kk@realm.io
  5. Codable (Decodable & Encodable) • SwiftωΠςΟϒͷσʔλߏ଄ʢClass, Structure, EnumerationʣΛγ ϦΞϥΠζՄೳͳσʔλʹ૬ޓʹม׵͢Δ࢓૊ΈΛఏڙ͢Δ •

    DecodableʹରԠ͍ͯ͠ΔͱγϦΞϥΠζՄೳͳσʔλʢJSONͳ Ͳʣ͔ΒΦϒδΣΫτ΁ͷม׵͕ՄೳʹͳΔɻEncodable͸ͦͷٯ ʢStructure => JSONͳͲʣ • ίϯύΠϥͷࢧԉ͕ड͚ΒΕΔʢϝιου΍Ωʔͷࣗಈੜ੒ͳͲʣ ͷͰγϯϓϧʹॻ͚Δʢ৔߹͕͋Δʣ kk@realm.io
  6. Codable (Decodable & Encodable) kk@realm.io struct Person: Codable { ...

    } let person = try! JSONDecoder().decode(Person.self, from: json) let json = try! JSONEncoder().encode(person)
  7. Codable (Decodable & Encodable) kk@realm.io struct Person: Codable { private

    enum CodingKeys : String, CodingKey { case fullName = “full_name" ... } ... }
  8. 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)
  9. 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
  10. 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) } }
  11. 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) } }
  12. 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) } }
  13. 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 ...
  14. 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>
  15. 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
  16. 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 ... }
  17. 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]) } ...
  18. 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>() }
  19. 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" } // ❌
  20. 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" } // ❌
  21. 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" } // ❌
  22. 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" } // ❌
  23. 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" } // ❌
  24. 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" } // ❌
  25. 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" } // ❌
  26. 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
  27. 102 Platforms State of the Union 402 What's New in

    Swift 212 What's New in Foundation kk@realm.io Reference
  28. UIKit • Drag & Drop • Browse Files • App

    Password Autofill • Larger Navigation Bar Titles • Safe Area Layout Guides kk@realm.io
  29. 102 Platforms State of the Union 201 What's New in

    Cocoa Touch 203 Introducing Drag and Drop kk@realm.io Reference
  30. AppStore • New Store UI • Phased Releases • Start

    In-App Purchases From the App Store kk@realm.io
  31. 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
  32. 703 Introducing Core ML 710 Core ML in depth 506

    Vision Framework: Building on Core ML kk@realm.io Reference
  33. PDFKit • PDFView • PDFThumbnailView • PDFDocument • PDFPage •

    PDFOutline • PDFSelection • PDFAnnotation • PDFAction kk@realm.io
  34. 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
  35. 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
  36. kk@realm.io pdfView.displayMode = .singlePage pdfView.displayMode = .singlePageContinuous pdfView.displayMode = .twoUp

    pdfView.displayMode = .twoUpContinuous pdfView.displayDirection = .vertical pdfView.displayDirection = .horizontal PDFDisplayMode/PDFDisplayDirection
  37. 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)
  38. 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)
  39. kk@realm.io if let documentURL = Bundle.main.url(forResource: "...", withExtension: "pdf") {

    if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Draw custom contents
  40. 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
  41. kk@realm.io if let string = document.string { ... } Extract

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

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

    if let document = PDFDocument(url: documentURL) { ... document.delegate = self ... Search text (Asynchronous)
  45. 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) ... }