Search code examples
iosmultithreadingswiftswift2nsthread

Swift 2 - iOS - Dispatch back to originating thread


So I have an application that fires a series of asynchronous events and then writes the results to a buffer. The problem is that I want the buffer to be written to synchronously (in the thread that spawned the asynchronous process)

skeleton code is as such

let Session = NSURLSession.sharedSession()
let TheStack = [Structure]()
//This gets called asynchronously, e.g. in threads 3,4,5,6,7
func AddToStack(The Response) -> Void { 
   TheStack.insertAt(Structure(The Response), atIndex: 0))
   if output.hasSpaceAvailable == true {
      // This causes the stream event to be fired on mutliple threads
      // This is what I want to call back into the original thread, e.g. in thread 2
      self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) 
   }
}

// This is in the main loop, e.g. thread 2
func stream(aStream: NSStream, handleEvent: NSStreamEvent) {

   switch(NSStreamEvent) {

      case NSStreamEvent.OpenCompleted:
          // Do some open stuff
      case NSStreamEvent.HasBytesAvailable:
          Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack)
      case NSStreamEvent.HasSpaceAvailable:
          // Do stuff with the output
      case NSStreamEvent.CloseCompleted:
          // Close the stuff
   }
}

The problem is the thread that calls is dataTaskWithRequest is in thread, say, 3. The completion handler fires in many different threads and causes case NSStreamEvent.HasSpaceAvailable: to be running in thread 3, plus all the threads that they existed in.

My question is: How do I make it so that self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable) is called in thread 3, or what-ever the original thread was to prevent this tripping over of each other in the output phase.

Thanks in advance!

NOTE: The thread that contains the input/output handling was created with NSThread.detachNewThreadSelector


Solution

  • Alright, for the curious onlooker I, with aid from comments to the question I have figured out how to do what I originally asked in the question (whether or not this ultimately gets rewritten to use GCD is a different question)

    The solution (with a slightly increased scope into the code) is to use performSelector with a specific thread.

    final class ArbitraryConnection {
    
    internal var streamThread: NSThread
    
    let Session = NSURLSession.sharedSession()
    let TheStack = [Structure]()
    //This gets called asynchronously, e.g. in threads 3,4,5,6,7
    func AddToStack(The Response) -> Void { 
       TheStack.insertAt(Structure(The Response), atIndex: 0))
       if output.hasSpaceAvailable == true {
          // This causes the stream event to be fired on multiple threads
          // This is what I want to call back into the original thread, e.g. in thread 2
    
          // Old way
          self.stream(self.output, handleEvent: NSStreamEvent.hasSpaceAvailable)
          // New way, that works
          if(streamThread != nil) {
              self.performSelector(Selector("startoutput"), onThread: streamThread!, withObject: nil, waitUntilDone: false)
          }
       }
    }
    
    func open -> Bool {
        // Some stuff
        streamThread = NSThread.currentThread()
    }
    
    
    final internal func startoutput -> Void {
       if(output.hasSpaceAvailable && outputIdle) {
            self.stream(self.output, handleEvent: NSStreamEvent.HasSpaceAvailable)
       }
    }
    // This is in the main loop, e.g. thread 2
    func stream(aStream: NSStream, handleEvent: NSStreamEvent) {
    
       switch(NSStreamEvent) {
    
          case NSStreamEvent.OpenCompleted:
              // Do some open stuff
          case NSStreamEvent.HasBytesAvailable:
              Session.dataTaskWithRequest(requestFromInput, completionHandler: AddToStack)
          case NSStreamEvent.HasSpaceAvailable:
              // Do stuff with the output
          case NSStreamEvent.CloseCompleted:
              // Close the stuff
       }
    }
    }
    

    So use performSelector on the object with the selector and use the onThread to tell it what thread to pass to. I check both before performing the selector and before doing the call to make sure that output has space available (make sure I don't trip over myself)