Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Delegates / Events coming from C# background

Tags:

ios

swift

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?

like image 515
Jaydoug Avatar asked Mar 18 '23 11:03

Jaydoug


1 Answers

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

Closures

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

Delegates

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
like image 58
David Rönnqvist Avatar answered Apr 01 '23 16:04

David Rönnqvist