$30 off During Our Annual Pro Sale. View Details »

Swift Solutions

Swift Solutions

In this talk we start by looking at where we came from with Objective-C, and how we feel about Swift. This presentation is about trying to discover what idiomatic Swift is by looking at a series of examples with "Swift" solutions.

Ben Scheirman

April 10, 2015
Tweet

More Decks by Ben Scheirman

Other Decks in Programming

Transcript

  1. View Slide

  2. @subdigital

    View Slide

  3. View Slide

  4. View Slide

  5. Objec&ve(C

    View Slide

  6. [ ]

    View Slide

  7. 2009

    View Slide

  8. View Slide

  9. View Slide

  10. C#

    View Slide

  11. BCSItem *item = [[BCSItem alloc] initWithIdentifier:@"id1"];
    [item favorite];

    View Slide

  12. °□°#

    View Slide

  13. NSFileManager *fm = [NSFileManager defaultManager];
    NSError *error = nil;
    BOOL result = [fm removeItemAtPath:path error:&error];
    if (!result) {
    NSLog(“The error was: %@“, [error localizedDescription]);
    }

    View Slide

  14. !

    View Slide

  15. Stockholm)Syndrome?

    View Slide

  16. Objec&ve(C*has*some*really*great*features.

    View Slide

  17. View Slide

  18. 2014

    View Slide

  19. View Slide

  20. Swi$%is%different.

    View Slide

  21. Swi$%is
    • clean'&'concise
    • memory'managed
    • safe
    • func3onal'(?)

    View Slide

  22. Why$do$we$need$a$safe$language?

    View Slide

  23. remember%goto%fail?

    View Slide

  24. no?

    View Slide

  25. hashOut.data = hashes + SSL_MD5_DIGEST_LEN;
    hashOut.length = SSL_SHA1_DIGEST_LEN;
    if ((err = SSLFreeBuffer(&hashCtx)) != 0)
    goto fail;
    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
    if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;
    err = sslRawVerify(...);
    . . .

    View Slide

  26. hashOut.data = hashes + SSL_MD5_DIGEST_LEN;
    hashOut.length = SSL_SHA1_DIGEST_LEN;
    if ((err = SSLFreeBuffer(&hashCtx)) != 0)
    goto fail;
    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail; <----------
    if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;
    err = sslRawVerify(...);
    . . .

    View Slide

  27. Hidden&backdoor&to
    root$in$OS$X
    !!
    !!!h#ps:/
    /truesecdev.wordpress.com/2015/04/09/hidden;backdoor;api;to;root;privileges;in;apple;os;x/

    View Slide

  28. !
    !!h#ps:/
    /twi#er.com/tlrobinson/status/586202234582507520

    View Slide

  29. “Swi%&Sucks”

    View Slide

  30. “What&the&hell&were&they&thinking?”

    View Slide

  31. "Let’s'implement'our'own'open1
    source'Swi4"

    View Slide

  32. View Slide

  33. Swi$%is%unique

    View Slide

  34. Idioma'c)Swi,

    View Slide

  35. View Slide

  36. We#are#s'll#learning#what#idioma'c#
    Swi4#is.

    View Slide

  37. So#let’s#take#a#look#at#some#cases#from#a#Swi1#
    viewpoint.

    View Slide

  38. Lazy%Proper+es%with%Closures

    View Slide

  39. func viewDidLoad() {
    super.viewDidLoad()
    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    session = NSURLSession(
    configuration: config,
    delegate: self,
    delegateQueue: nil)
    navigationItem.leftBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .Bookmarks,
    target: self,
    action: "bookmarks:")
    navigationItem.rightBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .Add,
    target: self,
    action: "add:")
    ...
    }

    View Slide

  40. @interface MyViewController ()
    @property (nonatomic, strong) NSURLSessionConfiguration *configuration;
    @end
    - (NSURLSessionConfiguration *)configuration {
    if (_configuration == nil) {
    _configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    return _configuration;
    }

    View Slide

  41. lazy var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()

    View Slide

  42. lazy var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    lazy var session = ...

    View Slide

  43. lazy var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    lazy var session: NSURLSession = {
    }()

    View Slide

  44. lazy var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    lazy var session: NSURLSession = {
    return NSURLSession(configuration: self.configuration,
    delegate: self,
    delegateQueue: nil)
    }()

    View Slide

  45. lazy var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    lazy var session: NSURLSession = {
    return NSURLSession(configuration: self.configuration,
    delegate: self,
    delegateQueue: nil)
    }()
    lazy var leftBarButtonItem: UIBarButtonItem = {
    return UIBarButtonItem(
    barButtonSystemItem: .Bookmarks,
    target: self,
    action: "bookmarks:")
    }()
    lazy var rightBarButtonItem: UIBarButtonItem = {
    return UIBarButtonItem(
    barButtonSystemItem: .Add,
    target: self,
    action: "add:")
    }()

    View Slide

  46. func viewDidLoad() {
    super.viewDidLoad()
    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    session = NSURLSession(
    configuration: config,
    delegate: self,
    delegateQueue: nil)
    navigationItem.leftBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .Bookmarks,
    target: self,
    action: "bookmarks:")
    navigationItem.rightBarButtonItem = UIBarButtonItem(
    barButtonSystemItem: .Add,
    target: self,
    action: "add:")
    ...
    }

    View Slide

  47. func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.leftBarButtonItem = leftBarButtonItem
    navigationItem.rightBarButtonItem = rightBarButtonItem
    }

    View Slide

  48. Returning)errors

    View Slide

  49. var error: NSError? = nil
    let object = NSJSONSerialization.JSONObjectWithData(data,
    options: nil,
    error: &error
    )

    View Slide

  50. class func JSONObjectWithData(data: NSData,
    options: NSJSONReadingOptions,
    error: NSErrorPointer) -> AnyObject

    View Slide

  51. func JSONObjectWithData(
    data: NSData,
    options: NSJSONReadingOptions = nil,
    ) -> (AnyObject?, NSError?)

    View Slide

  52. let result = JSONObjectWithData(data)
    result.0 // object?
    result.1 // error?

    View Slide

  53. let (object, error) = JSONObjectWithData(data)
    if let someObject = object {
    // can work with someObject
    } else {
    // error
    }

    View Slide

  54. func JSONObjectWithData(
    data: NSData,
    options: NSJSONReadingOptions = nil,
    ) -> (obj: AnyObject?, error: NSError?)

    View Slide

  55. let result = JSONObjectWithData(data)
    result.obj // object?
    result.error // error?

    View Slide

  56. Some%mes'if let'sucks

    View Slide

  57. Swi$%Pa(ern%Matching

    View Slide

  58. switch(result) {
    case (.Some(let x), nil):
    println("x's type is AnyObject")
    case (nil, .Some(let e)):
    println("e's type is NSError")
    default:
    }

    View Slide

  59. A"more"general"solu-on

    View Slide

  60. enum Result {
    case Value(T)
    case Error(NSError)
    }

    View Slide

  61. func JSONObjectWithData(
    data: NSData,
    options: NSJSONReadingOptions = nil
    ) -> Result

    View Slide

  62. func JSONObjectWithData(
    data: NSData,
    options: NSJSONReadingOptions = nil
    ) -> Result {
    ....
    if let error = parserError {
    return .Error(error)
    } else {
    return .Value(parsedObject)
    }
    }

    View Slide

  63. let result = JSONObjectWithData(data)
    switch(result) {
    case .Value(let obj):
    println("we parsed the object: \(obj)")
    case .Error(let error):
    println("error parsing json: \(error)")
    }

    View Slide

  64. (Side&Note:&Boxing&currently&required)
    Hopefully)Apple)will)fix)this)soon,)but)currently)you)need)to)box)the)
    T)value)for)this)to)work:
    class Box {
    var unbox: T
    init(_ value: T) {
    unbox = value
    }
    }

    View Slide

  65. enum Result {
    case Value(Box)
    case Error(NSError)
    }
    switch result {
    case .Value(let box):
    let val = box.unbox
    ...
    }

    View Slide

  66. More%on%Result%later...

    View Slide

  67. Map$/$Reduce

    View Slide

  68. View Slide

  69. struct Item {
    let name: String
    let price: Double
    let taxable: Bool
    }

    View Slide

  70. Given&a&Shopping&cart&full&of&items...
    let shoppingCart = [
    Item(name: "Apples", price: 0.67, taxable: false),
    Item(name: "Bananas", price: 1.87, taxable: false),
    Item(name: "Beer", price: 6.99, taxable: true),
    Item(name: "Candy", price: 4.00, taxable: true),
    Item(name: "Ice Cream", price: 6.00, taxable: true)
    ]

    View Slide

  71. Compute(the(amount(of(tax(you(
    have(to(pay...

    View Slide

  72. let TaxRate = 0.0825

    View Slide

  73. let TaxRate = 0.0825
    var tax = 0.0

    View Slide

  74. let TaxRate = 0.0825
    var tax = 0.0
    for item in shoppingCart {
    }
    println(tax)

    View Slide

  75. let TaxRate = 0.0825
    var tax = 0.0
    for item in shoppingCart {
    if item.taxable {
    }
    }
    println(tax)

    View Slide

  76. let TaxRate = 0.0825
    var tax = 0.0
    for item in shoppingCart {
    if item.taxable {
    tax += item.price * TaxRate
    }
    }
    println(tax)

    View Slide

  77. How$can$we$express$this$in$terms$of$
    map,$filter,$and$reduce?

    View Slide

  78. map

    View Slide

  79. func map(
    source: C,
    transform: (C.Generator.Element) -> T) -> [T]

    View Slide

  80. func map(
    source: S,
    transform: (S.Generator.Element) -> T) -> [T]

    View Slide

  81. let items = [1, 2, 3]
    map(items, { (item: Int) -> Int in
    return item * 2
    })

    View Slide

  82. let items = [1, 2, 3]
    map(items, { (item) -> Int in
    return item * 2
    })

    View Slide

  83. let items = [1, 2, 3]
    map(items, { item in
    return item * 2
    })

    View Slide

  84. map(items, {
    return $0 * 2
    })

    View Slide

  85. map(items, { $0 * 2 })

    View Slide

  86. map(items) { $0 * 2 }

    View Slide

  87. [1,2,3].map { $0 * 2 }

    View Slide

  88. [1,2,3].map { $0 * 2 } // => [2, 4, 6]

    View Slide

  89. filter

    View Slide

  90. func filter(
    source: S,
    includeElement: (S.Generator.Element) -> Bool)
    -> [S.Generator.Element]

    View Slide

  91. [1,2,3].filter { $0 % 2 == 0 }

    View Slide

  92. [1,2,3].filter { $0 % 2 == 0 } // => [2]

    View Slide

  93. reduce

    View Slide

  94. func reduce(
    sequence: S,
    initial: U,
    combine: @noescape (U, S.Generator.Element) -> U) -> U

    View Slide

  95. [1,2,3].reduce(0) {
    (sum, n) in
    return sum + n
    }

    View Slide

  96. All#set?

    View Slide

  97. First&get&the&taxable&items
    let taxable = shoppingCart.filter { $0.taxable }

    View Slide

  98. Map$items$to$their$prices
    let taxable = shoppingCart.filter { $0.taxable }
    let taxableAmounts = taxable.map { $0.price }

    View Slide

  99. Reduce&to&a&single&value&(the&amount&of&tax)
    let taxable = shoppingCart.filter { $0.taxable }
    let taxableAmounts = taxable.map { $0.price }
    let tax = taxableAmounts.reduce(0) {
    (tax, itemPrice) in
    return tax + (itemPrice * TaxRate)
    }

    View Slide

  100. Can$simplify$the$reduce$block...
    let taxable = shoppingCart.filter { $0.taxable }
    let taxableAmounts = taxable.map { $0.price }
    let itemTaxes = taxable.map { $0 * TaxRate }
    let tax = itemTaxes.reduce(0) {
    (tax, itemTax) in
    return tax + (itemTax)
    }

    View Slide

  101. ...into&an&exis+ng&func+on

    View Slide

  102. func +(lhs: Int, rhs: Int) -> Int {
    return lhs + rhs
    }

    View Slide

  103. Look$at$the$Shape$of$the$func/on...
    (T,T) -> U

    View Slide

  104. Look$at$the$Shape$of$the$func/on...
    !-> T

    View Slide

  105. (T,T)-> T

    View Slide

  106. (U,T)-> U

    View Slide

  107. Can$just$use$the$+$func-on...
    let taxable = shoppingCart.filter { $0.taxable }
    let taxableAmounts = taxable.map { $0.price }
    let itemTaxes = taxable.map { $0 * TaxRate }
    let tax = itemTaxes.reduce(0, combine: +)

    View Slide

  108. ..."and"chain"them"together...
    let tax = shoppingCart
    .filter { $0.taxable }
    .map { $0.price }
    .map { $0 * TaxRate }
    .reduce(0, combine: +)
    println(tax) // 1.40

    View Slide

  109. Func%ons
    (no$interim$state,$no$loops,$no$condi1onals)

    View Slide

  110. func taxFunction(rate: Double)

    View Slide

  111. func taxFunction(rate: Double) -> ([Item]) -> Double {

    View Slide

  112. func taxFunction(rate: Double) -> ([Item]) -> Double {
    return { (items) in
    return items
    .filter { $0.taxable }
    .map { $0.price }
    .map { $0 * rate }
    .reduce(0, combine: +)
    }
    }

    View Slide

  113. let texasTax = taxFunction(0.0825)
    let tax = texasTax(items)

    View Slide

  114. Flat%Map

    View Slide

  115. We#know#what#map#is,#what#is#fla1en?
    flatten([[5, 6], [4], [7, 8]]) -> [5,6,4,7,8]

    View Slide

  116. it#is#o&en#handy#to#fla.en#before#
    mapping

    View Slide

  117. (flat,&map)

    View Slide

  118. struct Person {
    var name: String
    var emails: [String]
    }

    View Slide

  119. "Give&me&a&list&of&all&email&
    addresses"

    View Slide

  120. people.map { $0.emails } ???

    View Slide

  121. There%is%no%fla,en%func0on,%but%we%could%
    write%one...
    func flatten(array: [[T]) -> [T] {
    var result = [T]()
    for item in array {
    for subItem in item {
    result.append(subItem)
    }
    }
    return result
    }

    View Slide

  122. ...ore%more%succinctly,%with%reduce:
    func flatten(array: [[T]) -> [T] {
    return array.reduce([], combine: +)
    }

    View Slide

  123. Yay!%Swi)%1.2%has%a%flatMap%
    func3on%:)

    View Slide

  124. [[1, 2], [3], [4, 5]].flatMap { $0 }
    // -> [1, 2, 3, 4, 5]

    View Slide

  125. people.flatMap { $0.emails }

    View Slide

  126. flatMap&for&Result&?

    View Slide

  127. enum Result {
    case Value(Box)
    case Error(NSError)
    func flatMap(next: T -> Result) -> Result {
    switch self {
    case .Value(let box):
    let val = box.unbox
    return next(val)
    case .Error(let err):
    return .Error(err)
    }
    }
    }

    View Slide

  128. func getValue() -> Result
    func convertToFloat(i: Int) -> Result
    func squareRoot(x: Float) -> Result

    View Slide

  129. getValue().flatMap(convertToFloat).flatMap(squareRoot)

    View Slide

  130. Dealing(with(Index(Paths

    View Slide

  131. Remember&sta*c&table&views&before&
    Storyboards?

    View Slide

  132. func tableView(tableView: UITableView,
    cellForRowAtIndexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
    if indexPath.row == 0 {
    //
    } else {
    // ...
    }
    } else if indexPath.section ==
    numberOfSectionsInTableView(tableView) - 1 {
    // ...
    } else {
    // ...
    }
    }

    View Slide

  133. func tableView(tableView: UITableView,
    cellForRowAtIndexPath: NSIndexPath) -> UITableViewCell {
    switch (indexPath) {
    case (0, 0): ...
    case (0, let row): ...
    case (let section, let row) where isLastSection(section):
    ...
    case (let section, let row):
    ...
    }
    }

    View Slide

  134. Enums&for&API&Endpoints

    View Slide

  135. Consider)an)API)with)some)paths:
    • /notifications
    • /users/:username
    • /catgories/:category_id/products/:product_id

    View Slide

  136. enum ApiEndpoints {
    case Notifications
    case UserProfile(String)
    case ProductDetail(Int, Int)
    }

    View Slide

  137. enum ApiEndpoints {
    case Notifications
    case UserProfile(String)
    case ProductDetail(Int, Int)
    var path: String {
    switch(self) {
    case .Notifications: return "/notifications"
    case .UserProfile(let username):
    return "/users/\(username.stringByAddingPercentEscapes...)"
    case .ProductDetail(let categoryId, let productId):
    return "/categories/\(categoryId)/products/\(productId)"
    }
    }
    }

    View Slide

  138. ApiClient.request(.UserProfile("subdigital")) {
    ...
    }

    View Slide

  139. Swi$%is%s'll%new

    View Slide

  140. !

    View Slide

  141. "When&we&learn&a&new&programming&
    language,&we&may&need&[to]&discard&
    what&we&thought&it&should&be,&and&
    learn&what&it&will&be."
    —"a"Swi'"student

    View Slide

  142. thanks!
    @subdigital+•+@nsscreencast

    View Slide