Slide 1

Slide 1 text

Asynchronous*Programming*in*Swi2 Thomas'Visser,'Highstreet April&21,&2015

Slide 2

Slide 2 text

BrightFutures Thomas'Visser,'Highstreet April&21,&2015

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

BrightFutures+is+a+simple+Futures+&+ Promises+library+for+iOS+and+OS+X+ wri

Slide 5

Slide 5 text

func logIn(name: String, pwd: String) -> Future { } 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 }

Slide 6

Slide 6 text

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? } }

Slide 7

Slide 7 text

Things'that'are'hard'work • error%handling • asynchronous%programming • declaring%data%flow%instead%of%more%state • leveraging%the%power%of%func8onal%concepts

Slide 8

Slide 8 text

Things'that'BrightFutures'is'good'for • error%handling • asynchronous%programming • declaring%data%flow%instead%of%more%state • leveraging%the%power%of%func8onal%concepts

Slide 9

Slide 9 text

Error$Handling

Slide 10

Slide 10 text

Error$Handling var error: NSError? let results = context.executeFetchRequest(request, error: &error)

Slide 11

Slide 11 text

Error$Handling let results = context.executeFetchRequest(request, error: nil)

Slide 12

Slide 12 text

Error$Handling var error: NSError? let results = context.executeFetchRequest(request, error: &error)

Slide 13

Slide 13 text

Error$Handling func fetchProducts(error: NSErrorPointer) -> [Product]? { return (context.executeFetchRequest(request, error: error) as! [Product]) }

Slide 14

Slide 14 text

Error$Handling • There's(the(old(way • There's(no((stdlib)(new(way((yet) • Excep9ons(are(gone

Slide 15

Slide 15 text

Introducing:,Result

Slide 16

Slide 16 text

Introducing:,Result public enum Result { case Success(Box) case Failure(NSError) }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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) }

Slide 19

Slide 19 text

map public enum Result { case Success(Box) case Failure(NSError) public func map(f:T -> U) -> Result }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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 }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Asynchronous*programming

Slide 28

Slide 28 text

Get$off$the$main$thread! —"Sincerely,"UIKit

Slide 29

Slide 29 text

// 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()

Slide 30

Slide 30 text

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()

Slide 31

Slide 31 text

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()

Slide 32

Slide 32 text

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()

Slide 33

Slide 33 text

Introducing:,Future

Slide 34

Slide 34 text

class Future { var result: Result? = nil }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

class Future { var result: Result? = nil func onComplete(callback: Result -> ()) -> Self func onSuccess(callback: T -> ()) -> Self func onFailure(callback: FailureCallback) -> Self func map(f: (T) -> U) -> Future func flatMap(f: T -> Future) -> Future func flatMap(f: T -> Result) -> Future }

Slide 37

Slide 37 text

func dataFromURL(url: String) -> Future 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) } } }

Slide 38

Slide 38 text

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) } } }

Slide 39

Slide 39 text

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 }

Slide 40

Slide 40 text

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 } }

Slide 41

Slide 41 text

On#the#Origin#of#Futures

Slide 42

Slide 42 text

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 { let p = Promise() Queue.global.async { let nthFib = fibonacci(n) p.success(nthFib) } return p.future }

Slide 43

Slide 43 text

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 }

Slide 44

Slide 44 text

func execute(request: R) -> Future { let p = Promise() 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 }

Slide 45

Slide 45 text

class ImageFetcher { var fetchedImages = [String:Future]() func fetchImage(path: String) -> Future { if !fetchedImages[path] { fetchedImages[path] = actuallyFetchImage(path) } return fetchedImages[path] } private func actuallyFetchImage(path: String) -> Future { // implementation } }

Slide 46

Slide 46 text

let products: [Future] = 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) }

Slide 47

Slide 47 text

Future • Release'1.0.0 • Produc1on'usage • Integra1on'with'CocoaTouch'/'3rd'party'libs • FutureProofing:'hCps:/ /github.com/Thomvis/FutureProofing' • Progress'&'cancella1on:' • GoodProgress:'hCps:/ /github.com/Thomvis/GoodProgress

Slide 48

Slide 48 text

Thank&you! • BrightFutures:-h.ps:/ /github.com/Thomvis/BrightFutures • Me:-h.ps:/ /twi.er.com/thomvis88 • Highstreet:-h.p:/ /highstreetapp.com