Basic Principles of Asynchronicity
PodioPlatformKit’s main responsibility os making request to the Podio API. To do so, it needs to provide a mean of handling asynchronous
method calls. PodioPlatformKit uses a future-based approach where each asynchronous method returns an instance of PKTAsyncTask
. You can then register callbacks (onSuccess(_)
, onError(_)
and onComplete(_)
) on this tasks that will be called according to the following rules:
- If the task has not yet completed, the callback will be kept and executed on the main thread upon the completion of the task. Registered callbacks on a task are not guaranteed to be called in the order they were registerd.
- If the task has alread completed, the callback will be executed immediately with the result of the task.
- A task can only succeed or fail, not both.
- a task can only succeed or fail once, it cannot be retried or reset.
To register a callback, you have three options:
The onSuccess(_)
method is used to register callbacks to be executed if the task succeeds, meaning it does not generate an error:
let task = SomeClass.someAsynchronousMethod()
task.onSuccess { (response: PKTResponse!) in
// The task finished successfully
}
The onError(_)
method can be used to register callbacks for the error case:
let task = SomeClass.someAsynchronousMethod()
task.onSuccess { (error: NSError?) in
// The task failed and returned an error
}
If you are interested in both the success and error case at the same time, you can use the onComplete(_)
method to register a callback for when either happens:
let task = SomeClass.someAsynchronousMethod()
task.onComplete { (response: PKTResponse?, error: NSError?) in
if let error = error {
// Task failed
} else {
// Task succeeded
}
}];
The main advantage of modelling asynchronisity with futures is that you can chain tasks together to acheive some things. PodioPlatformKit provides a few combinator methods to combine sub-tasks in to bigger tasks. Consider for example if you first want to upload a task, then attach it to an object. With a callback approach you would have to nest your callback handlers:
let data: NSData = ...; // Some image data
PKTFile.uploadWithData(data, fileName:@"image.jpg", completion: { (file: PKTFile?, error: NSError?) in
if let file = file {
file.attachWithReferenceID(1234, referenceType: .Item completion: { (response: PKTResponse?, error: NSError?) in
if error == nil {
// Handle success
} else {
// Handle failure
}
})
} else {
// Handle failure
}
})
You can see that we have to handle the error case twice. With a task based approach, we can instead use the pipe(_)
combinator method which takes a block that generates a new task based on the result of the first task once completed:
let data: NSData = ...; // Some image data
let task = PKTFile.uploadWithData(data, fileName:@"image.jpg")
.pipe { (file: PKTFile?) in
return file.attachWithReferenceID(1234, referenceType: .Item)
}
task.onComplete { (response: PKTResponse?, error: NSError?) in
if let error = error {
// Handle failure
} else {
// Handle success
}
}];
Here, we only have to handle the error case once and we do not end up with deeply nested code blocks. There are also other useful combinator methods such as when(_)
, then(_)
and map(_)
available.