Pass data from asynchronous child task to View Controller

I'm just looking for some advice here regarding decoupling of view from model. I have a BLE class which does its thing and i instantiate it naturally in the main View Controller. One of the things application does is just display some of information from a scan in a text field. I want to update the view as soon as the delegate method fires in the BLE class, the only way I could think to acheive that was to pass in a reference to the text field as part of the constructor. Then I can just dispatch a request to the main thread to update my view.


class BLEScanner: NSObject, CBCentralManagerDelegate {
    
    var central: CBCentralManager?
    var peripherals : [(CBPeripheral,Int)] = []
    var scanOutput: NSTextField?
    
    init(scanOutput: NSTextField) {
        self.scanOutput = scanOutput
        super.init()
        let centralQueue = dispatch_queue_create("sup", DISPATCH_QUEUE_SERIAL)
        central = CBCentralManager(delegate: self, queue: centralQueue)
    }


This doesn't really seem right to pass a reference to a text field, is there a better way to do this?

You're totally right. Model-layer code should not care one bit about what happens in the view and controller layers. The correct way to abstract this is to use a closure—a self-contained block of code that you receive as a method parameter and then store to call once the scan is complete. Here's how I would write your class:

class BLEScanner: NSObject, CBCentralManagerDelegate {

     var central: CBCentralManager?
     var peripherals : [(CBPeripheral, Int)] = []
     var completionHandler: (String -> Void)

     init(completionHandler handler:(String? -> Void)) {
          super.init()
          completionHandler = handler
          ...


Now that the "store" part is changed, here's how I would change the completion code inside your dispatch to the main thread:

let result = //however your result string is determined
completionHandler(result)


So now you have a way to run any code you want once the scan is complete. So now you need to use that closure to update your text field from inside your controller object. Here's how you might write that:

let completionHandler = {(result:String?) -> Void in
  //assume textField is an outlet to your interface text field
  textField.stringValue = result
}


The cool part about this design pattern is that it's modular and avoids linking the model and the view layers. If you ever decide that you need more advanced behavior than just updating a text field, all you have to do is rewrite your completion handler. There's no need to change anything in the model code. Have a look at a lot of the system APIs that involve long-running tasks, and you'll see that almost all of them (the newer ones, at least) are designed like this.

Pass data from asynchronous child task to View Controller
 
 
Q