JThora JThora - 21 days ago 10
Swift Question

How to make a swift 3.0 class with a func that calls a callback?

I need simple ways to make a class that I can pass and call a callback function using Swift 3.0

I don't like to use anything like a Notification Centers or Selectors with # before them. I'm picky and I also don't like to type that much.

Example:

func setCallback(function:Callback){
self.callback = function
}

func callCallback(){
self.callback()
}

Rob Rob
Answer

Let's imagine you have some complex task and you want to supply a callback to be called when the task is done:

class ComplexTaskManager {
    var completionHandler: (() -> ())?

    func performSomeComplexTask() {
        ...

        // when done, call the completion handler

        completionHandler?()
    }
}

So, you'd use it like:

let manager = ComplexTaskManager()

manager.completionHandler = {
    // this is what I want to do when the task is done
}

manager.performSomeComplexTask()

Now, I'm imagining that we're dealing with some time consuming asynchronous task for which you want to call the callback when it's done. But there are other, similar patterns (e.g. perhaps you're iterating through some model object and you want to call your callback for every instance within that object), but the idea is the same as above.


If you have a strong reference cycle, you can fix that with weak references (e.g. supply [weak self] or [unowned self] to the closure). For example, imagine:

class ViewController {
    let manager = ComplexTaskManager()

    @IBOutlet weak var statusLabel: UILabel!

    override viewDidLoad() {
        super.viewDidLoad()

        statusLabel.text = "Starting complex task"

        manager.completionHandler = { [weak self] in 
            self?.statusLabel.text = "Done"
        }

        manager.performSomeComplexTask()
    }
}

But you only need to do this if there is a strong reference cycle (e.g. the view controller is keeping strong reference to the manager and because of the presence of self inside the closure, the manager is keeping a strong reference back to the view controller). If either of these are local references rather than properties, though, there is no strong reference cycle to break.


Frankly, the more common pattern for a closure that will be called once per task is to supply it as a parameter of the method. For example:

class ComplexTaskManager {
    func performSomeComplexTask(completionHandler: () -> ()) {
        ...

        // when done, call the completion handler

        completionHandler()
    }
}

So, you'd use it like:

let manager = ComplexTaskManager()

manager.performSomeComplexTask() {
    // this is what I want to do when the task is done
}

The beauty of this approach is that it's not only simpler, but avoids the risk of strong reference cycles.