2014-10-22 61 views
8

我有我的代碼在其他項目的工作,在一類具有以下特徵:NSStreamDelegate不接受NSStreamEvent.HasSpaceAvailable:

class ViewController: UIViewController, NSStreamDelegate, UITextFieldDelegate { 

然後我搬到了它自己的類連接,這樣我就可以潛在地重用在每個連接:

class XMPPConnection: NSObject, NSStreamDelegate 

當我這樣做,我感動了所有viewDidLoad()代碼爲init()。我也嘗試將這個init代碼放在一個單獨的函數中,並在實例化該類之後調用該函數。這並沒有改變任何事情。

我可以在2個項目中,新與舊之間切換,只是爲了確保它不是一個服務器的問題,這樣做,確認它不是。

每次運行應用程序後,結果都不一樣。它或者不叫HasSpaceAvailable,只是坐在那裏,或者在我的類class AppDelegate: UIResponder, UIApplicationDelegate, FBLoginViewDelegate的線程1上出現(lldb)錯誤。儘管這個錯誤可能與Facebook整合有關,但lldb沒什麼可看的。然而,與其他項目不同,每次運行都不會調用HasSpaceAvailable

這裏的NSStreamDelegate的完整的代碼,所以沒有混亂。這個類意味着成爲一個使用XMPP協議的相當標準的連接方法。

import UIKit 
import Foundation 


class XMPPConnection: NSObject, NSStreamDelegate { //NSObject 

    var input : NSInputStream? 
    var output: NSOutputStream? 

    //let XMLStream: String = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' version='1.0' xmlns='jabber:client' to='mydomain.com' xml:lang='en' xmlns:xml='http://www.w3.org/XML/1998/namespace'>" 
    let XMLStream: String = "<stream:stream to='mydomain.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>" 
    var XMLAuth: String? 
    let XMLStreamEnd: String = "</stream:stream>" 
    let XMLResource: String = "<iq type='set' id='bind_1'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>OneSide</resource></bind></iq>" 
    let XMLSession: String = "<iq type='set' id='sess_1'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>" 
    let XMLStartTLS: String = "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"; 
    var messagesToBeSent:[String] = [] 
    var lastSentMessageID = 0 
    var lastReceivedMessageID = 0 


    init(facebookID: String) { 
     super.init() 
     let username = "[email protected]" //should hash device ID 
     let password = "123456" //hash it 
     var UTF8AuthStr = "\0\(username)\0\(password)".dataUsingEncoding(NSUTF8StringEncoding) 
     let Base64Str = UTF8AuthStr!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.fromRaw(0)!) 
     XMLAuth = "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>\(Base64Str)</auth>" 
     //println(XMLAuth) 

     self.connectToSocket("mydomain.com", port: 5222) 
     send(self.XMLStream) 
     //send(self.XMLStartTLS) 
     /*send(self.XMLAuth!) 
     send(self.XMLStream) 
     send(self.XMLResource) 
     send(self.XMLSession)*/ 
     //sendMessage("hi") 


    } 

    func connectToSocket(host: String, port: Int) { 

     NSStream.getStreamsToHostWithName(host, port: port, inputStream: &(self.input), outputStream: &(self.output)) 

     self.input!.delegate = self 
     self.output!.delegate = self 

     self.input!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 
     self.output!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 

     self.input!.open() 
     self.output!.open() 

     println("Connected") 


     //let bytesWritten = self.output!.write(UnsafePointer(data.bytes), maxLength: data.length) 

     //println(bytesWritten) 


    } 

    //The delegate receives this message when a given event has occurred on a given stream. 
    func stream(theStream: NSStream!, handleEvent streamEvent: NSStreamEvent) { 

     println("Message received") 

     switch streamEvent { 
     case NSStreamEvent.None: 
      println("NSStreamEvent.None") 
     case NSStreamEvent.OpenCompleted: 
      println("NSStreamEvent.OpenCompleted") 
     case NSStreamEvent.HasBytesAvailable: 
      println("NSStreamEvent.HasBytesAvailable") 
      if let inputStream = theStream as? NSInputStream { 
       //println("is NSInputStream") 
       if inputStream.hasBytesAvailable { 
        //println("hasBytesAvailable") 
        let bufferSize = 1024 
        var buffer = Array<UInt8>(count: bufferSize, repeatedValue: 0) 

        var bytesRead: Int = inputStream.read(&buffer, maxLength: bufferSize) 
        //println(bytesRead) 
        if bytesRead >= 0 { 
         lastReceivedMessageID++ 
         var output: String = NSString(bytes: &buffer, length: bytesRead, encoding: NSUTF8StringEncoding) 
         //println("output is") 
         println(output) 
        } else { 
         println("error") 
         // Handle error 
        } 
       } 
      } 
     case NSStreamEvent.HasSpaceAvailable: 
      println("NSStreamEvent.HasSpaceAvailable") 
      send(nil) //send next item 
      //send next message or 
      //what if there is no next message to send, and instead waiting user input? 
     case NSStreamEvent.ErrorOccurred: 
      println("NSStreamEvent.ErrorOccurred") 
     case NSStreamEvent.EndEncountered: 
      println("NSStreamEvent.EndEncountered") 
     default: 
      println("default") 
     } 
    } 

    func send(message:String?){ 
     if (self.output!.hasSpaceAvailable){ //stream ready for input 
      //println("true hasSpaceAvailable") 
      var data:NSData 
      var thisMessage:String 
      if message == nil{ // no message specified 
       if messagesToBeSent.count != 0{ //messages waiting to be sent 
        thisMessage = messagesToBeSent[0] 
        data = messagesToBeSent[0].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 
        messagesToBeSent.removeAtIndex(0) 
       } 
       else{ //no data to be sent 
        //no message specified and nothing to be sent 
        return 
       } 
      } 
      else{ 
       thisMessage = message! 
       data = message!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 
      } 

      //println("Sent the following") 
      wait() 
      let bytesWritten = self.output!.write(UnsafePointer(data.bytes), maxLength: data.length) 
      lastSentMessageID++ 
      //println(thisMessage) 
      //println("Message sent to server and response is") 
      //println(bytesWritten) //int count 
     } 
     else{ //steam busy 
      println("no space available in stream") 
      if message != nil{ 
       messagesToBeSent.append(message!) 
      } 
     } 
    } 

    func sendMessage(message:String, from:String, to:String){ 
     let xmlMessage = "<message to='\(to)@mydomain.com' from='\(from)@mydomain.com' type='chat' xml:lang='en'> <body>\(message)</body></message>" 
     send(xmlMessage) 
    } 


    func wait() { 
     while true { 
      //println("waiting") 
      if lastSentMessageID == lastReceivedMessageID { 
       break 
      } 

      NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1)); 
      NSThread.sleepForTimeInterval(0.1) 
     } 
    } 

} 

所以我可以看到2件事情可能造成這種情況。要麼把它移到它自己的類中,並且創建它的一個實例,或者改變繼承。關於第一個方法可行思考,我期待到的代碼穿線行:self.input!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)

檢查streamStatus.toRaw()後,它說1這是NSStreamStatusOpening。我不確定這是否會改變。

回答

3

如果XMPPConnection存儲在局部變量, 例如我可以重現該問題

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

    let conn = XMPPConnection(facebookID: "...") 
    return true 
} 

當此方法返回時,實例將被釋放。另一方面流代理仍然指向實例,這會導致崩潰。的NSStreamdelegate 屬性被聲明爲

unowned(unsafe) var delegate: NSStreamDelegate? 

這是「分配」又名「unsafe_unretained」雨燕等同。因此 設置委託不保留該對象,並且釋放對象 不會將該屬性設置爲nil(對於弱引用)。

如果該實例被存儲在屬性,例如

class AppDelegate: UIResponder, UIApplicationDelegate { 

    var window: UIWindow? 
    var conn : XMPPConnection! 

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

     conn = XMPPConnection(facebookID: "...") 
     return true 
    } 

    // ... 
} 

然後你的代碼在我的測試中正常工作。

-1
  CFStreamCreatePairWithSocketToHost(nil, serverAddress, Server, &readStream, &writeStream) 

CFStreamCreatePairWithSocketToHost(nil, website!.host, Server, &readStream, &writeStream) 

    CFReadStreamSetProperty(readStream!.takeUnretainedValue(), kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) 
    CFWriteStreamSetProperty(writeStream!.takeUnretainedValue(), kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue) 
      inputStream = readStream!.takeUnretainedValue(); 
      outputStream = writeStream!.takeUnretainedValue();