Search code examples
iosswiftasynchronoussynchronizationgcdasyncsocket

GCDAsyncSocket for Synchronous Requests with Swift 1.2


Background:

I'm using GCDAsyncSocket on a project successfully with Swift 1.2 (via a bridging header).

The challenge right now is that it needs some sort of queue because the system it's connecting to can only process and return one command at a time.

So if it calls methods back to back, for example:

getSystemInfo()
getSystemStatus()

Only getSystemInfo() is returned via the delegate callback because the system was busy processing it, however, the getSystemStatus() was sent asynchronously successfully but not processed by the controller. I'd like it to be able to make the calls back to back and have them queue and processed once the controller is done processing and returning back the previous response -- basically making the process synchronous.

Question:

As you can see below in the example code under, didConnectToHost delegate callback, when it connects to the controller, it calls getSystemInfo() then getSystemStatus() back to back, it should call getSystemStatus() after it gets the results from the system info.

I have been looking at NSCondition, NSOperation, even GCD, but I'm not sure what the most elegant way to approach this is. I don't want to put yet another queue processor in the mix since there already is a queue setup for the GCDAsyncSocket. What is the best, most elegant way to handle this?

Pseudo Class Code:

public class SendNet: NSObject, GCDAsyncSocketDelegate {

    var socket:GCDAsyncSocket! = nil

    func setupConnection(){

            var error : NSError?
            if (socket == nil) {
                socket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
            } else {
                socket.disconnect()
            }


            if (!socket.connectToHost(host, onPort: port, withTimeout: 5.0, error: &error)){
                println("Error: \(error)")
            } else {
                println("Connecting...")
            }
    }

    public func socket(socket : GCDAsyncSocket, didConnectToHost host:String, port p:UInt16) {

       println("Connected to \(host) on port \(p).")

       self.socket = socket

       getSystemInfo()
       getSystemStatus()

    }

    func send(msgBytes: [UInt8]) {

        var msgData = NSData(bytes: msgBytes, length: msgBytes)
        socket.writeData(msgData, withTimeout: -1.0, tag: 0)
        socket.readDataWithTimeout(-1.0, tag: 0)

    }

    func getSystemInfo() {

        var sendBytes:[UInt8] = [0x0, 0x1, 0x2, 0x3]
        send(sendBytes)

    }

    func getSystemStatus() {

        var sendBytes:[UInt8] = [0x4, 0x5, 0x6, 0x7]
        send(sendBytes)

    }


    public func socket(socket : GCDAsyncSocket!, didReadData data:NSData!, withTag tag:Int){

         var msgData = NSMutableData()
         msgData.setData(data)

         var msgType:UInt16 = 0
         msgData.getBytes(&msgType, range: NSRange(location: 2,length: 1))

         println(msgType)

      }
}

Any suggestions would be great -- thanks!


Solution

  • So I decided to use NSOperation for this.

    Created a class file called SyncRequest.swift with the following code:

    import Foundation
    
    class SyncRequest : NSOperation {
    
     var socket:GCDAsyncSocket! = nil
     var msgData:NSData! = nil
    
     override var concurrent: Bool {
       return false
     }
    
     override var asynchronous: Bool {
       return false
     }
    
     private var _executing: Bool = false
     override var executing: Bool {
        get {
            return _executing
        }
        set {
            if (_executing != newValue) {
                self.willChangeValueForKey("isExecuting")
                _executing = newValue
                self.didChangeValueForKey("isExecuting")
            }
        }
    }
    
    private var _finished: Bool = false;
    override var finished: Bool {
        get {
            return _finished
        }
        set {
            if (_finished != newValue) {
                self.willChangeValueForKey("isFinished")
                _finished = newValue
                self.didChangeValueForKey("isFinished")
            }
        }
    }
    
    /// Complete the operation
    func completeOperation() {
        executing = false
        finished  = true
    }
    
    override func start() {
        if (cancelled) {
            finished = true
            return
        }
    
        executing = true
    
        main()
    }
    
    
     override func main() -> (){
        println("starting...")
    
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReadData:", name: "DidReadData", object: nil)
    
        sendData()
    }
    
    
        func sendData() {
            socket.writeData(msgData, withTimeout: -1.0, tag: 0)
            println("Sending: \(msgData)")
            socket.readDataWithTimeout(-1.0, tag: 0)
        }
    
    
        func didReadData(notif: NSNotification) {
            println("Data Received!")
    
            NSNotificationCenter.defaultCenter().removeObserver(self, name: "DidReadData", object: nil)
    
            completeOperation()
    
        }
    
    }
    

    Then, when I need to send something out to the controller I do the following:

    // sync the request to the controller
    let queue = NSOperationQueue() // sync request queue
    let requestOperation = SyncRequest()
    requestOperation.socket = socket // pass the socket  to send through
    requestOperation.msgData = msgData // pass the msgData to send
    queue.maxConcurrentOperationCount = 1
    queue.addOperation(requestOperation)
    

    Don't forget to send the NSNotification when data comes in from where you are handling the GCDAsyncSocket's "didReadData" delegate.

    public func socket(socket : GCDAsyncSocket!, didReadData data:NSData!, withTag tag:Int){
    
      ...
      NSNotificationCenter.defaultCenter().postNotificationName("DidReadData", object: data)
      ...
    
    }