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

Embrace Immutability

Keith Smiley
February 15, 2016

Embrace Immutability

Keith describes an open source library for parsing JSON written at Lyft, how it enables you to use immutable model objects, and why that's a good thing.

Keith Smiley

February 15, 2016
Tweet

More Decks by Keith Smiley

Other Decks in Programming

Transcript

  1. EMBRACE IMMUTABILITY

    View full-size slide

  2. GITHUB.COM/LYFT/MAPPER

    View full-size slide

  3. NOT A JSON PARSER

    View full-size slide

  4. NO MODEL -> JSON

    View full-size slide

  5. {
    "user": {
    "id": "123",
    "photoURL": "http://example.com/123.jpg",
    "phone": {
    "verified": false
    }
    }
    }

    View full-size slide

  6. struct User: Mappable {
    var id: String?
    var photoURL: NSURL?
    var verified: Bool = true
    init() {}
    mutating func map(mapper: Mapper) {
    id « mapper["id"]
    photoURL « mapper["photoURL"]
    verified « mapper["phone.verified"]
    }
    }

    View full-size slide

  7. protocol Mapper {
    func baseType(field: T?) -> T?
    func baseTypeArray(field: [T]?) -> [T]?
    // ...
    subscript(keyPath: String) -> Mapper { get }
    }

    View full-size slide

  8. func « (inout left: T?, mapper: Mapper) {
    left = mapper.baseType(left)
    }
    func « (inout left: [T]?, mapper: Mapper) {
    left = mapper.baseTypeArray(left)
    }

    View full-size slide

  9. class MapperFromJSON: Mapper {
    var JSON: NSDictionary
    var currentValue: AnyObject?
    // ...
    subscript(keyPath: String) -> Mapper {
    get {
    self.currentValue = self.JSON.valueForKeyPath(keyPath)
    return self
    }
    }
    }

    View full-size slide

  10. func baseType(field: T?) -> T? {
    let value = self.currentValue
    switch T.self {
    case is NSURL.Type where value is String:
    return NSURL(string: value as! String) as? T
    // ...
    default:
    return value as? T
    }
    }

    View full-size slide

  11. case is CLLocationCoordinate2D.Type:
    if let castedValue = value as? [String: Double],
    let latitude = castedValue["lat"],
    let longitude = castedValue["lng"]
    {
    return CLLocationCoordinate2D(latitude: latitude,
    longitude: longitude) as? T
    }
    return nil

    View full-size slide

  12. id = JSON["id"].string

    View full-size slide

  13. struct User: Mappable {
    let id: String
    let photoURL: NSURL?
    let verified: Bool
    init(map: Mapper) throws {
    try id = map.from("id")
    photoURL = map.optionalFrom("photoURL")
    verified = map.optionalFrom("phone.verified") ?? false
    }
    }

    View full-size slide

  14. func from(field: String) throws -> T {
    if let value = self.JSONFromField(field) as? T {
    return value
    }
    throw MapperError()
    }

    View full-size slide

  15. extension NSURL: Convertible {
    static func fromMap(value: AnyObject?) throws -> NSURL {
    if let string = value as? String,
    let URL = NSURL(string: string)
    {
    return URL
    }
    throw MapperError()
    }
    }

    View full-size slide

  16. struct AppInfo {
    let hints: [HintID: Hint]
    init(map: Mapper) throws {
    try hints = map.from("hints",
    transformation: Transform.toDictionary { $0.id })
    }
    }

    View full-size slide

  17. func from(field: String,
    transformation: AnyObject? throws -> T) rethrows -> T
    {
    return try transformation(self.JSONFromField(field))
    }

    View full-size slide

  18. EMBRACE IMMUTABILITY

    View full-size slide

  19. DUMB MODELS
    struct User: Mappable {
    let id: String
    let photoURL: NSURL?
    init(map: Mapper) throws {
    try id = map.from("id")
    photoURL = map.optionalFrom("photoURL")
    }
    }

    View full-size slide

  20. CODE SMELL
    - let pickup: Place?
    + var pickup: Place?

    View full-size slide

  21. SEPARATE CREATION
    struct Ride: Mappable {
    var pickup: Place?
    }

    View full-size slide

  22. RideManager
    Observer1 Observer2 Observer3
    UIChanges Network Call
    Evil Function

    View full-size slide

  23. RideManager.ride.pickup = Place(name: "Realm")

    View full-size slide

  24. RideManager
    Observer1 Observer2 Observer3
    UIChanges Network Call
    Evil Function

    View full-size slide

  25. RideManager
    Observer1 Observer2 Observer3
    UIChanges Network Call
    Evil Function

    View full-size slide

  26. SEPARATE CREATION
    struct RideRequest {
    var pickup: Place?
    }

    View full-size slide

  27. 1. How can I do this?
    2. How can I do this easier?
    3. How can I do this simpler?
    4. How can I not do this?

    View full-size slide

  28. GITHUB.COM/LYFT/MAPPER

    View full-size slide