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

Michael Gray: Futures and Promises, or how I learned to stop worrying and love the GCD

Realm
June 13, 2016

Michael Gray: Futures and Promises, or how I learned to stop worrying and love the GCD

Presented at AltConf 2016

Realm

June 13, 2016
Tweet

More Decks by Realm

Other Decks in Programming

Transcript

  1. What are doing • What the heck is a Promise?

    a Future? • How is FutureKit different from all the others? • Some Advanced Used cases.
  2. Lots of Options • FutureKit (Swift) • PromiseKit (Objective-C/Swift) •

    BrightFutures (Swift) • Bolts/BFTask (Objective-C) • …
  3. blocks / closures • Introduced into Objective-C for 10.6, iOS4.

    • SO MUCH Better than delegate protocols! • iOS 5.0 SDK started to include ‘callback blocks’ in api calls instead of delegate protocols! • GCD made easy: dispatch_async()! -
  4. blocks / closures func asyncFunction(callback:(Result) -> Void) -> Void {

    dispatch_async(queue) { let x = Result("Hello!") callback(x) } }
  5. The rise of the callback block • Apple introduces lots

    of API calls that use callbacks. • AFNetworking becomes the #1 Objective-C library! Cause it uses callbacks instead of horrible NSURLConnection delegates! • We can get our stuff out of the main q (dispatch_async())
  6. Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]) .response { request, response, data,

    error in print(request) print(response) print(data) print(error) } classic example
  7. The problem of callbacks • 1 - Where does my

    callback run? Is it in a background queue? main queue? Could it be either? Do I have to add my own defensive dispatch_async? • 2 - Error Handling? Two different callbacks? Enumerations? • 3 - What if I cancel? Should my callback still be called? Do I get an error? (Never been consistent..)
  8. func getMyDataObject(url: NSURL,callback:(object:MyCoreDataObject?,error: ErrorType) -> Void) { Alamofire.request(.GET, url, parameters:

    ["foo": "bar"]).responseJSON { response in switch response.result { case .Success: managedContext.performBlock { let myObject = MyCoreDataObject(entity: entity!, insertIntoManagedObjectContext: managedContext) do { try myObject.addData(response) try managedContext.save() dispatch_async(dispatch_get_main_queue(), { callback(object: myObject, error: nil) }) } catch let error { callback(object: nil,error: error) } } case .Failure(let error): callback(object: nil,error: error) } } }
  9. Futures & Promise • Why Futures/Promises? • Escape from Callback

    Hell. • The functions that define callbacks = anti-pattern
  10. Promise vs Future • Why are there are two terms?

    Can’t we all just agree? • Javascript - Promises. then()/catch()/finally() • Scala (Future/Promise pair). onComplete()/onSuccess()/onFail()
  11. Future vs Promise (FutureKit) • Future - Consumer interface. Usually

    returned from a function, instead of a callback. TypeSafe. A consumer can then “attach” completion blocks to create even more interesting Futures. • Promise - the Producer interface. Is used to create a Future. Think - “Always keep your Promises”. (Always complete your promises, or your code will hang). Can be used to ‘wrap’ any existing callback code and convert it into a Future.
  12. FutureKit func getMyDataObject(url: NSURL) -> Future<MyCoreDataObject> { let executor =

    Executor.ManagedObjectContext(managedContext) Alamofire.request(.GET, url, parameters: ["foo": "bar"]) .futureJSONObject() .onSuccess(executor) { response -> MyCoreDataObject in let myObject = MyCoreDataObject(entity: entity!, insertIntoManagedObjectContext: managedContext) try myObject.addData(response) try managedContext.save() return MyCoreDataObject } } }
  13. onComplete let newFuture = sampleFuture.onComplete { (result: FutureResult<Int>) -> Completion<String>

    in switch result { case let .Success(value): return .Success(String(value)) case let .Fail(err): return .Fail(err) case .Cancelled: return .Cancelled } }
  14. FutureResult<T> public enum FutureResult<T> { case Success(T) case Fail(ErrorType) case

    Cancelled } A FutureResult is the FINAL value that a Future will return (when it is done)
  15. Completion<T> public enum Completion<T> { case Success(T) case Fail(ErrorType) case

    Cancelled case CompleteUsing(Future<T>) } A Completion type is a value that can ‘complete’ a Promise!
  16. onSuccess let asyncFuture5 = Future(.Background) { () -> Int in

    return 5 } let f = asyncFuture5.onSuccess(.Main) { (value) -> Int in let five = value print("\(five)") return five }
  17. onSuccess to new type let stringFuture = Future<String>(success: "5") stringFuture


    .onSuccess { (stringResult:String) -> Int in let i = Int(stringResult)! return i } .map { intResult -> [Int] in let array = [intResult] return array } .onSuccess { arrayResult -> Int? in return arrayResult.first! }
  18. Futures are VERY composable! func coolFunctionThatAddsOneInBackground(num : Int) -> Future<Int>

    { // let's dispatch this to the low priority background queue return Future(.Background) { () -> Int in let ret = num + 1 return ret } } let stringFuture = Future<Int>(success: 5) stringFuture
 .onSuccess { intResult -> Future<Int> in return coolFunctionThatAddsOneInBackground(intResult) } .onSuccess { return coolFunctionThatAddsOneInBackground($0) }
  19. Promise let namesPromise = Promise<[String]>() let names = ["Skyler","David","Jess"] namesPromise.completeWithSuccess(names)

    let namesFuture :Future<[String]> = namesPromise.future namesFuture.onSuccess(.Main) { (names : [String]) -> Void in for name in names { print("Happy Future Day \(name)!") } }
  20. func getCoolCatPic(catUrl: NSURL) -> Future<UIImage> { let catPicturePromise = Promise<UIImage>()

    let task = NSURLSession.sharedSession().dataTaskWithURL(catUrl) { (data, response, error) -> Void in if let e = error { catPicturePromise.completeWithFail(e) } else { if let d = data, image = UIImage(data: d) { catPicturePromise.completeWithSuccess(image) } else { catPicturePromise.completeWithErrorMessage("didn't understand response from \ (response)") } } } task.resume() // return the promise's future. return catPicturePromise.future }
  21. What about Error Handling • You want a function that

    ‘fetches’ a ‘Model’ object from server. • Make an API call (Networking Errors).
 Parse JSON (Json Parsing errors)
 Save to the DB (File IO Errors, DB Validation Errors). • As you add more logic, you will have more potential errors.
  22. Why isn’t FutureKit ErrorType specific? • A Future that defines

    it’s ErrorType.. undermines composability. • By forcing consumers to ‘expect’ any error, it means you can modify any Futures. • side effect: You MUST add error handling.
  23. built in ‘catch’ handling • All FutureKit handlers onComplete/onSuccess/onFail/onCancel/ map..

    all ‘catch’! • Just ‘throw’ errors (no try/catch needed).
  24. onFail func getMyDataObject(url: NSURL) -> Future<MyCoreDataObject> { let executor =

    Executor.ManagedObjectContext(managedContext) Alamofire.request(.GET, url) .futureJSONObject() .onSuccess(executor) { response -> MyCoreDataObject in let myObject = MyCoreDataObject(entity: entity, insertIntoManagedObjectContext: managedContext) try myObject.addData(response) try managedContext.save() return MyCoreDataObject }.onFail { error in // alert user of error! } }
  25. onFail • onFail does NOT CONSUME ERRORS. 
 Always use

    onComplete to remap Errors. • Only adds side effects. • Calling onSuccess chains without onFail or onSuccess = compilers warning!
  26. Cancellation • Cancellation is it’s OWN result type. • Cancellations

    are NOT errors - they are normal and expected behavior. • onCancel() handler doesn’t pollute your onFail() handlers.
  27. Cancellation public extension AlamoFire.Request { public func future<T: ResponseSerializerType>(responseSerializer s:

    T) -> Future<T.SerializedObject> { let p = Promise<T.SerializedObject>() p.onRequestCancel { _ in self.cancel() return .Continue } self.response(queue: nil, responseSerializer: s) { response -> Void in switch response.result { case let .Success(t): p.completeWithSuccess(t) case let .Failure(error): let e = error as NSError if (e.domain == NSURLErrorDomain) && (e.code == NSURLErrorCancelled) { p.completeWithCancel() } else { p.completeWithFail(error) } } } return p.future } }
  28. The new Swift anti-patterns • NO MORE functions that need

    callback properties! • Futures with ‘optional’ return values - Use Fail instead! • Let functions define their own execution context!
  29. Executors • Where do my callback blocks run? • Executors

    - (ExecutionContext, Bolt Executor). • (Hint): it’s a usually just CGD queue.
  30. Executors in Action let asyncFuture5 = Future(.Background) { () ->

    Int in return 5 } let f = asyncFuture5.onSuccess(.Main) { (value) -> Int in let five = value print("\(five)") return five }
  31. Special Executors • .Immediate - my block can run anywhere!

    Use with care, but good for simple value-mapping.
 
 .MainImmediate - Will try to avoid unneeded dispatch_async if already in the Main
 .MainAsync - always does a dispatch_async()
 .Queue(q) - your own custom dispatch_queue
 .OperationQueue(opq) - wraps NSOperationQueue
 .ManagedObjectContext(c) - wraps ‘performBlock()’
  32. Extra Smart • .Current = Use the Executor that is

    currently running! (If not running in Executor, than maps to .Main or .Async). May not work correctly with .Custom • .Primary = The Context to use if you don’t define one (default = .Current) Configurable • .Main = .MainImmediate/.MainAsync (default: .MainImmediate). • .Async = Configurable ‘not-main-queue’. Default: .Default (qos default) • .Custom = build your own!
  33. let ex: Executor = .Background let f = ex.execute {

    let data = expensiveToComputeData() return data } execute() (instead of a Promise)
  34. combineFutures let shiftFuture = model.getShift() let vehicleFuture = model.getVehicle() combineFutures(shiftFuture,

    vehicleFuture) .onSuccess { (shift,vehicle) -> Void in print("\(shift),\(vehicle)") }
  35. CompletionType protocol public protocol CompletionType { associatedtype T var completion

    : Completion<T> { get } } Can be used to ‘wrap’ any of your favorite Async objects (ex: AlamoFire request)
  36. Caching Futures NSCache.findOrFetch(key, expireTime:NSDate? = nil, onFetch: () -> Future<T>)

    you can ‘cache’ the result of an operation - BEFORE it’s complete.
  37. FutureBatch • When combineFuture() doesn’t work. • Can take an

    ‘array’ of Futures (of any size) and perform parallel execution. • Much more control over failures and cancellations of sub futures.
  38. FutureFIFO • A simple ‘queue’ of Asynchronous operations let fifo:

    FutureFIFO() let f:Future<UIImage> = fifo.add { return functionThatBuildsUImage() }
  39. NSOperations FutureOperation<T> - Build an NSOperation using a Future NSOperationQueue.add<T>(block:

    () throws -> (Future<T>)) -> Future<T> let opQueue: NSOperationQueue let f:Future<UIImage> = opQueue.add { return functionThatBuildsUImage() }
  40. Kill Your Delegate Protocols! • Define interactive modal components that

    single ‘future’ property that returns a result. • Need more than one type of result? Use an enumerated value!. • example Model viewControllers that return their results via a Future.
  41. Reactive? public extension SignalProducerType { public func mapFuture<U>(transform: Value ->

    Future<U>?) -> SignalProducer<U?, Error> } Do I use a Signal/Sequence or a Future? Future -> Signal A-OK! Signal -> Future Caution!