I had my code working in another project, in a class with the following signature:
class ViewController: UIViewController, NSStreamDelegate, UITextFieldDelegate {
Then I moved the connection to it's own class, so I can potentially reuse it in each connection:
class XMPPConnection: NSObject, NSStreamDelegate
When I did this, I moved all the viewDidLoad()
code into init()
. I also tried putting that init
code in a separate function, and calling that function after instantiating the class. That did not change anything.
I can switch between the 2 projects, the old and new, just to make sure that it's not a server problem, and doing that confirms that it's not.
After each run of the application, the result is different. It either does not call the HasSpaceAvailable
and just sits there, or there is a (lldb)
error thrown on thread 1 in my class class AppDelegate: UIResponder, UIApplicationDelegate, FBLoginViewDelegate
. That error may be related to Facebook integration though, but with lldb
there is not much to look at. However, with each and every run, HasSpaceAvailable
is never called, unlike the other project.
Here's the full code of the NSStreamDelegate, so there is no confusion. This class is a meant to be a fairly standard method of connection using XMPP protocol.
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)
}
}
}
So I can see 2 things that may have caused this. Either moving it into it's own class, and making an instance of it, or the change of inheritance. Thinking about the first possiblity, I am looking into the threading lines of code: self.input!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
After checking the streamStatus.toRaw()
, it says 1
which is NSStreamStatusOpening
. I'm not sure if this ever changes.
I could reproduce the problem if the XMPPConnection
is stored in a local variable,
e.g.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let conn = XMPPConnection(facebookID: "...")
return true
}
The instance is deallocated when this method returns. On the other hand the stream
delegates still point to the instance, this causes the crashes. The delegate
property of NSStream
is declared as
unowned(unsafe) var delegate: NSStreamDelegate?
which is the Swift equivalent of "assign" aka "unsafe_unretained". Therefore
setting the delegate does not retain the object, and deallocating the object
does not set the property to nil
(as for weak references).
If the instance is stored in a property, e.g.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var conn : XMPPConnection!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
conn = XMPPConnection(facebookID: "...")
return true
}
// ...
}
then your code worked correctly in my test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With