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

Michael Gray: Futures and Promises, or how I le...

Avatar for Realm Realm
June 13, 2016

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

Presented at AltConf 2016

Avatar for Realm

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!