Nevyn Bengtsson (of Lookback, previously Spotify) presents his take on Futures/Promises/Tasks in Swift, and the problems with not abstracting asychronous code.
Code: https://github.com/nevyn/SPAsync/blob/master/Swift/main.swift
Blog: http://overooped.com/post/41803252527/methods-of-concurrency
Transcript: https://raw.githubusercontent.com/nevyn/SPAsync/master/Meta/SLUG%20Lightning%202015%20notes.txt
3. Nested scopes
!.fetchFromNetwork(input) { intermediate in
!.parseResponse(intermediate) { again in
dispatch_async(dispatch_get_main_queue()) {
updateUI(output)
}
}
}
4. Error handling
!.fetchFromNetwork(input, callback: { intermediate in
!.parseResponse(intermediate, callback: { again in
dispatch_async(dispatch_get_main_queue()) {
updateUI(output)
}
}, errback: { error in
displayError("when parsing response, ", error)
})
}, errback: { error in
// This error is VERY far away from fetchFromNetwork!
displayError("when fetching from network, ", error)
})
5. Cancellation
var cancelled = false
block var cancellable : Cancellable?
let operation = !.fetchFromNetwork(input, callback: { intermediate in
if(cancelled) return
cancellable = !.parseResponse(intermediate, callback: { again in
if(cancelled) return
...
})
})
func cancel() {
cancelled = true
operation.cancel()
cancellable?.stopOperation()
}
14. SPTask.swift 2/4
class Task<T> {
public func addCallback(
on queue: dispatch_queue_t,
callback: (T -> Void)
) -> Self
public func addErrorCallback(
on queue: dispatch_queue_t,
callback: (NSError! -> Void)
) -> Self
public func addFinallyCallback(
on queue: dispatch_queue_t,
callback: (Bool -> Void)
) -> Self
}
15. Callback example
// Two of these three are executed immediately after each other
network.fetch(resource).addCallback { json in
let modelObject = parse(json)
updateUI(modelObject)
}.addErrback { error in
displayDialog(error)
}.addFinally { cancelled in
if !cancelled {
viewController.dismiss()
}
}
16. SPTask.swift 3/4
class Task<T> {
public func then<T2>(on queue:dispatch_queue_t, worker: (T -> T2)) -> Task<T2>
public func then<T2>(chainer: (T -> Task<T2>)) -> Task<T2>
}
17. Chaining example
// A: inline background parsing on _worker_queue
func parse<T>(json) -> T
network.fetch(resource)
.then(on: _worker_queue) { json in
// First this function runs, running parse on _worker_queue...
return parse<MyModel>(json)
}.addCallback { modelObject in
// ... and when it's done, this function runs on main
updateUI(modelObject)
}.addErrorCallback { ... }
// B: background parsing on Parser's own thread with async method
class Parser {
func parse<T>(json) -> Task<T>
}
network.fetch(resource)
.then(_parser.parse) // parser is responsible for doing async work on its own
.addCallback(updateUI) // and then updateUI is called with the model object
.addErrorCallback(displayError)
19. cancelandawaitAllexample
let imagesTask = Task.awaitAll(network.fetchImages(resource)).then { imageDatas in
return Task.awaitAll(imageDatas.map { data in
return parseImage(data)
})
}.addCallback { images in
showImages(image)
}
func viewDidDisappear()
{
// All downloading and parsing is cancelled
imagesTask.cancel()
}