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

Asynchronous Programming in Swift / BrightFutures

Asynchronous Programming in Swift / BrightFutures

I gave this talk at the CocoaHeadsNL meetup in April 2015. It gives an introduction to BrightFutures (https://github.com/Thomvis/BrightFutures), my open source futures & promises implementation in Swift.

Thomas Visser

April 21, 2015
Tweet

More Decks by Thomas Visser

Other Decks in Technology

Transcript

  1. func logIn(name: String, pwd: String) -> Future<User> { } func

    fetchPosts(user: User) -> Future<[Post]> { } logIn(username,password).flatMap { user in fetchPosts(user) }.onSuccess { posts in // do something with the user's posts }.onFailure { error in // either logging in or fetching posts failed }
  2. logInWithCompletionHandler { (user: User?, error: NSError?) in if let user

    = user { fetchPostsWithCompletionHander(user) { posts, error in if let posts = posts { // hurray, do something with the posts } else if let error = error { // could not fetch posts, at least we have the user } else { // can this happen? } } } else if let error = error { // something went wrong } else { // can this happen? } }
  3. func fetchProducts() -> Result<[Product]> switch fetchProducts() { case .Success(let boxedProducts):

    // the products are in boxedProducts.value case .Failure(let error): // something went wrong }
  4. func fetchProducts() -> Result<[Product]> { var error: NSError? let products:

    [AnyObject]? = context.executeFetchRequest(request, error: &error) if let products = products as? [Product] { return .Success(Box(products)) } if error { return .Failure(error) } return .Failure(MissingDataError) }
  5. map public enum Result<T> { case Success(Box<T>) case Failure(NSError) public

    func map<U>(f:T -> U) -> Result<U> } func divide(a: Int, b: Int) -> Result<Int> let result: Result<Bool> = divide(13451,22).map { divOut in return divOut % 2 == 0 }
  6. map public enum Result<T> { case Success(Box<T>) case Failure(NSError) public

    func map<U>(f:T -> U) -> Result<U> } func divide(a: Int, b: Int) -> Result<Int> let result = divide(13451,22).map { divOut in return divide(divOut, 11) }
  7. map public enum Result<T> { case Success(Box<T>) case Failure(NSError) public

    func map<U>(f:T -> U) -> Result<U> } func divide(a: Int, b: Int) -> Result<Int> let result: Result<Result<Int>> = divide(13451,22).map { divOut in return divide(divOut, 11) }
  8. flatMap public enum Result<T> { case Success(Box<T>) case Failure(NSError) public

    func map<U>(f:T -> U) -> Result<U> public func flatMap<U>(f:T -> Result<U>) -> Result<U> { } } func divide(a: Int, b: Int) -> Result<Int> { } let result: Result<Int> = divide(13451,22).flatMap { divOut in return divide(divOut, 11) }
  9. func jsonFromResponseData(responseData: NSData) -> Result<[[String:Any]]> { ... } func parseProducts(json:

    [[String:Any]]) -> Result<[Product]> { ... } let productResult = jsonFromResponseData(data).flatMap { json in return parseProducts(json) } if let products = productResult.value { // do something with the products }
  10. func parseProduct(json: [String:Any]) -> Result<Product> { ... } func parseProducts(json:

    [[String:Any]]) -> Result<[Product]> { let parseResults = json.map { dict in return parseProduct(dict) } return // ?? }
  11. func parseProduct(json: [String:Any]) -> Result<Product> { ... } func parseProducts(json:

    [[String:Any]]) -> Result<[Product]> { let parseResults: [Result<Product>] = json.map { dict in return parseProduct(dict) } return sequence(parseResults) }
  12. // here's the definition: func dataTaskWithURL( url: NSURL, completion: ((NSData!,

    NSURLResponse!, NSError!) -> Void)? ) -> NSURLSessionDataTask // let's use it: session.dataTaskWithURL("http://bit.ly/1IPyYmP") { data, response, error in if error != nil { // something went wrong } else { // we can more or less safely use data and response now } }.resume()
  13. session.dataTaskWithURL("http://bit.ly/1IPyYmP") { result in switch result { case .Success(let box):

    // safe access to access box.value.0 and box.value.1 case .Failure(let error): // something went wrong } }.resume()
  14. session.dataTaskWithURL("http://bit.ly/1IPyYmP") { (result: Result<(NSData,NSURLResponse)>) in switch result { case .Success(let

    box): // safe access to access box.value.0 and box.value.1 case .Failure(let error): // something went wrong } }.resume()
  15. session.dataTaskWithURL("http://bit.ly/1IPyYmP") { result in if let (data, response) = result.value

    { jsonFromResponseData(data).flatMap { (json: [[String:Any]]) in return parseProducts(json) }.map { (products: [Product]) in for product in products { session.dataTaskWithURL(product.url) { result in // fetch something for each product }.resume() } // do something when all product.url's have been loaded? } } else { // something went wrong } }.resume()
  16. class Future<T> { var result: Result<T>? = nil func onComplete(callback:

    Result<T> -> ()) -> Self func onSuccess(callback: T -> ()) -> Self func onFailure(callback: FailureCallback) -> Self }
  17. class Future<T> { var result: Result<T>? = nil func onComplete(callback:

    Result<T> -> ()) -> Self func onSuccess(callback: T -> ()) -> Self func onFailure(callback: FailureCallback) -> Self func map<U>(f: (T) -> U) -> Future<U> func flatMap<U>(f: T -> Future<U>) -> Future<U> func flatMap<U>(f: T -> Result<U>) -> Future<U> }
  18. func dataFromURL(url: String) -> Future<NSData> let res = dataFromURL("http://bit.ly/1IPyYmP").flatMap {

    (data:NSData) in return jsonFromResponseData(data) }.flatMap { json in return parseProducts(json) }.flatMap { products: [Products] in return products.map { product in return dataFromURL(product.url).map { (data:NSData) in return (product, data) } } }
  19. let res: [Future<(Product,NSData)>] = dataFromURL("http://bit.ly/1IPyYmP").flatMap { (data:NSData) in return jsonFromResponseData(data)

    }.flatMap { json in return parseProducts(json) }.flatMap { products: [Products] in return products.map { product in return dataFromURL(product.url).map { (data:NSData) in return (product, data) } } }
  20. let res: [Future<(Product,NSData)>] = dataFromURL("http://bit.ly/1IPyYmP").flatMap { (data:NSData) in return jsonFromResponseData(data)

    }.flatMap { json in return parseProducts(json) }.flatMap { products: [Products] in return products.map { product in return dataFromURL(product.url).map { (data:NSData) in return (product, data) } } } sequence(res).onSuccess { (pd:[(Product, Data)]) in // do something with the products and their data }.onFailure { error in // something failed: // - retrieving bit.ly URL // - turning data into JSON // - turning JSON into products // - fetching additional data from the product's url }
  21. let res: [Future<(Product,NSData)>] = dataFromURL("...").flatMap { (data:NSData) in return jsonFromResponseData(data)

    }.flatMap { json in return parseProducts(json) }.flatMap { products: [Products] in return products.map { product in return dataFromURL(product.url).map { (data:NSData) in return (product, data) } } } sequence(res).onSuccess { (pd:[(Product, Data)]) in // do something with the products and their data }.onFailure { error in if let (product, data) = res.first?.value { // the first product did load, recover from the error by doing something with it } }
  22. func fibonacci(n: Int) -> Int { switch n { case

    0...1: return n default: return fibonacci(n - 1) + fibonacci(n - 2) } } func asyncFibonacci(n: Int) -> Future<Int> { let p = Promise<Int>() Queue.global.async { let nthFib = fibonacci(n) p.success(nthFib) } return p.future }
  23. future { // runs on a background thread (GCD's global

    queue) fibonacci(1000) }.onSuccess { i in // called on the main thread // i is the 1000th Fibonacci number }
  24. func execute<R: ParentAppRequest>(request: R) -> Future<R.ResponseType> { let p =

    Promise<R.ResponseType>() let sent = WKInterfaceController.openParentApplication(request.userInfo, reply: { (response, error) -> Void in if let error = error { p.failure(error) } else { p.complete(request.parseResponse(response)) } }) if !sent { p.failure(InfrastructureError.ParentAppCommunicationFailure.NSErrorRepresentation) } return p.future }
  25. class ImageFetcher { var fetchedImages = [String:Future<UIImage>]() func fetchImage(path: String)

    -> Future<UIImage> { if !fetchedImages[path] { fetchedImages[path] = actuallyFetchImage(path) } return fetchedImages[path] } private func actuallyFetchImage(path: String) -> Future<UIImage> { // implementation } }
  26. let products: [Future<Product>] = fetchProducts().recover { error in return retrieveProductsFromCache()

    } let sumPrice = FutureUtils.fold(products, zero: 0) { sum, product in return sum + product.price } let token = InvalidationToken() Queue.global.after(.In(2.0)) { token.invalidate() } products.zip(sumPrice).map(context: Queue.global.context) { products, sumPrice in // we're on the global queue here return sumPrice / products.count }.onSuccess(token: token) { averagePrice in // safely update the UI label.setText(averagePrice) }