I have a Business Logic class that returns an object after making a simple web service call. It is called when a button is tapped (in my view controller). This call to my Business Logic class may take a few seconds to run and I don't want to block the main thread.
How do I raise an event in my Business Logic class that my ViewController can subscribe to, so that I know it's time to process and display the object?
Different platforms do things differently. If you are thinking
How do I raise an event in my BL class that my ViewController can subscribe to ... ?
you are going to be fighting the frameworks and have both a hard time and overly complicated code for it.
Instead, what you should be asking is more along the lines of
How do I know when an asynchronous operation has completed?
The answer to that question, for Cocoa, is that you can either use a completion block/closure that gets invoked when the operation has completed, or a delegate callback with a method that gets called on the delegate when the operation has completed. (There are also more alternatives, but these are two of the most common).
A helpful read-up on when to use different communication mechanisms can be found in this objc.io article
It's a common design in Cocoa to have a closure as the last argument of an asynchronous operation, that gets called when the operation is completed and gets passed the data as arguments into the closure. If the operation can fail, it is also common to have a second argument for an optional error (and make the success data optional as well)
For example below (for clarity I defined a type alias for the closure):
typealias FooCompletion = (data: FooData?, error: NSError?) -> ()
func doSomething(completion: FooCompletion) {
// do asyncrounous work
let successful: Bool = // based on the operation's success
if successful {
completion(data: someValidData, error: nil)
} else {
let error = NSError() // fill with information about the error
completion(data: nil, error: error)
}
}
You could use such a method, like this (here with a trailing closure):
yourBusinessLogic.doSomething { data, error in // FooData?, NSError?
if let validData = data {
// it completed (and validData now holds the data from the operation)
} else {
// there was an error that should be handled
}
}
Another alternative, the way this was done in Cocoa for many years, is to define a delegate protocol and have a property for an optional delegate in the class that performs the asynchronous task. If the operation can both succeed and fail, it's common to have delegate methods for both cases:
protocol BusinessLogicDelegate {
func somethingDidFinish(data: FooData)
func somethingDidFail(error: NSError)
}
class BusinessLogic {
var delegate: BusinessLogicDelegate? // optional delegate
// ...
}
Then when finishing the optional task, one of the callbacks are sent to the delegate (if any)
func doSomething() {
// do asyncrounous work
let successful: Bool = // based on the operation's success
if successful {
delegate?.somethingDidFinish(someValidData)
} else {
let error = NSError() // fill with information about the error
delegate?.somethingDidFail(error)
}
}
In this case, you could assign the delegate to the business logic instance, call the asynchronous method and wait for the one of the callbacks:
yourBusinessLogic.delegate = yourDelegate // doesn't have to be set every time
yourBusinessLogic.doSomething()
// later on, one of the callbacks is called on the delegate
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With