diff --git a/README.md b/README.md index 9d655d3..b5f4dab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Socket.IO-Client-Swift Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1. -For Swift 1.2, use the 1.2 branch. (1.2 will be merged with master once Swift 1.2 is released) +For Swift 1.2 use the 1.2 branch. Installation ============ @@ -18,11 +18,12 @@ Constructor `init(socketURL: String, opts[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) Methods ------- -1. `socket.on(name:String, callback:((data:AnyObject?) -> Void))` - Adds a handler for an event. -2. `socket.onMultipleItems(name:String, callback:((data:NSArray?) -> Void))` - Adds a handler for an event that can have multiple items. Items are stored in an array. +1. `socket.on(name:String, callback:((data:AnyObject?) -> Void)) -> SocketAckHandler` - Adds a handler for an event. Returns a SocketAckHandler which can be used to ack an event. See example. +2. `socket.onMultipleItems(name:String, callback:((data:NSArray?) -> Void)) -> SocketAckHandler` - Adds a handler for an event that can have multiple items. Items are stored in an array. Returns a SocketAckHandler which can be used to ack an event. See example. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. -5. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. +6. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ @@ -32,6 +33,8 @@ Events 4. `reconnect` - Emitted when the connection is starting to reconnect. 5. `reconnectAttempt` - Emitted when attempting to reconnect. +Example +======= ```swift // opts can be omitted, will use default values let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ @@ -44,7 +47,7 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ // Socket Events socket.on("connect") {data in println("socket connected") - + // Sending messages socket.emit("testEcho") @@ -57,6 +60,18 @@ socket.on("connect") {data in true, ["test": "foo"], "bar") } +// Requesting acks, and adding ack args +socket.on("ackEvent") {data in + if let str = data as? String { + println("Got ackEvent") + } + + socket.emitWithAck("ackTest", "test").onAck {data in + println(data) + } + +}.ackWith("I got your event", "dude") + socket.on("disconnect") {data in if let reason = data as? String { println("Socket disconnected: \(reason)") @@ -101,7 +116,7 @@ socket.onMultipleItems("multipleItems") {data in println(obj["test"]) } } - + // Recieving binary socket.on("dataTest") {data in if let data = data as? NSData { diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift new file mode 100644 index 0000000..d3bfe77 --- /dev/null +++ b/SwiftIO/SocketAckHandler.swift @@ -0,0 +1,47 @@ +// +// SocketAckHandler.swift +// Socket.IO-Swift +// +// Created by Erik Little on 2/14/15. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +typealias AckCallback = (AnyObject?) -> Void + +class SocketAckHandler { + let ackNum:Int! + let event:String! + var ackData:[AnyObject]? + var callback:AckCallback? + + init(event:String, ackNum:Int = 0) { + self.ackNum = ackNum + self.event = event + } + + func onAck(callback:AckCallback) { + self.callback = callback + } + + func ackWith(data:AnyObject...) { + self.ackData = data + } +} \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 679044b..84ee896 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -25,16 +25,20 @@ import Foundation class SocketEvent { + let justAck:Bool! + var ack:Int? var args:AnyObject! lazy var currentPlace = 0 lazy var datas = [NSData]() var event:String! var placeholders:Int! - init(event:String, args:AnyObject?, placeholders:Int = 0) { + init(event:String, args:AnyObject?, placeholders:Int = 0, ackNum:Int? = nil, justAck:Bool = false) { self.event = event - self.args = args? + self.args = args self.placeholders = placeholders + self.ack = ackNum + self.justAck = justAck } func addData(data:NSData) -> Bool { @@ -62,46 +66,101 @@ class SocketEvent { } class func createMessageForEvent(event:String, withArgs args:[AnyObject], - hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?) -> String { + hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?, wantsAck ack:Int? = nil) -> String { var message:String var jsonSendError:NSError? if !hasBinary { if nsp == nil { - message = "42[\"\(event)\"" + if ack == nil { + message = "42[\"\(event)\"," + } else { + message = "42\(ack!)[\"\(event)\"," + } } else { - message = "42/\(nsp!),[\"\(event)\"" + if ack == nil { + message = "42/\(nsp!),[\"\(event)\"," + } else { + message = "42/\(nsp!),\(ack!)[\"\(event)\"," + } } } else { if nsp == nil { - message = "45\(datas)-[\"\(event)\"" + if ack == nil { + message = "45\(datas)-[\"\(event)\"," + } else { + message = "45\(datas)-\(ack!)[\"\(event)\"," + } } else { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + if ack == nil { + message = "45\(datas)-/\(nsp!),[\"\(event)\"," + } else { + message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"," + } } } - for arg in args { - message += "," - - if arg is NSDictionary || arg is [AnyObject] { - let jsonSend = NSJSONSerialization.dataWithJSONObject(arg, - options: NSJSONWritingOptions(0), error: &jsonSendError) - let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) + return self.completeMessage(message, args: args) + } + + class func createAck(ack:Int, withEvent event:String, withArgs args:[AnyObject], + withAckType ackType:Int, withNsp nsp:String, withBinary binary:Int = 0) -> String { + var msg:String + + if ackType == 3 { + if nsp == "/" { + msg = "43\(ack)[" - message += jsonString! - continue + return self.completeMessage(msg, args: args) + + } else { + msg = "43/\(nsp),\(ack)[" + + return self.completeMessage(msg, args: args) } - - if arg is String { - message += "\"\(arg)\"" - continue + } else { + if nsp == "/" { + msg = "46\(binary)-\(ack)[" + + return self.completeMessage(msg, args: args) + + } else { + msg = "46\(binary)-/\(nsp),\(ack)[" + + return self.completeMessage(msg, args: args) } + } + } + + private class func completeMessage(var message:String, args:[AnyObject]) -> String { + var err:NSError? + for arg in args { + + if arg is NSDictionary || arg is [AnyObject] { + let jsonSend = NSJSONSerialization.dataWithJSONObject(arg, + options: NSJSONWritingOptions(0), error: &err) + let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) - message += "\(arg)" + message += jsonString! as String + message += "," + continue } - return message + "]" + if arg is String { + message += "\"\(arg)\"" + message += "," + continue + } + + message += "\(arg)" + message += "," + } + + if message != "" { + message.removeAtIndex(message.endIndex.predecessor()) + } + return message + "]" } private func fillInArray(arr:NSArray) -> NSArray { diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 8a77b1f..a13add6 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,20 +23,25 @@ // THE SOFTWARE. class SocketEventHandler { + let ack:SocketAckHandler! let event:String! let callback:NormalCallback? let callbackMult:MultipleCallback? var multiEvent = false - init(event:String, callback:NormalCallback) { + init(event:String, callback:NormalCallback, ack:SocketAckHandler) { self.event = event self.callback = callback + self.callbackMult = nil + self.ack = ack } - init(event:String, callback:MultipleCallback) { + init(event:String, callback:MultipleCallback, ack:SocketAckHandler) { self.event = event self.callbackMult = callback + self.callback = nil self.multiEvent = true + self.ack = ack } func executeCallback(item:AnyObject?, items:NSArray? = nil) { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8c607f4..de32c7f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -28,11 +28,19 @@ typealias NormalCallback = (AnyObject?) -> Void typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { - let socketURL:String! - private let secure:Bool! + let socketURL:NSMutableString! + let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + let handleQueue = dispatch_queue_create("handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + private var ackHandlers = [SocketAckHandler]() + private var currentAck = -1 private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var pingTimer:NSTimer! + private var secure = false var closed = false var connected = false var connecting = false @@ -45,16 +53,15 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var sid:String? init(socketURL:String, opts:[String: AnyObject]? = nil) { - super.init() var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { self.secure = true - } else { - self.secure = false } + mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" + self.socketURL = mutURL // Set options @@ -96,10 +103,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = false var endpoint:String - if self.secure! { - endpoint = "wss://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + if self.secure { + endpoint = "wss://\(self.socketURL)/socket.io/?transport=websocket" } else { - endpoint = "ws://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + endpoint = "ws://\(self.socketURL)/socket.io/?transport=websocket" } self.io = SRWebSocket(URL: NSURL(string: endpoint)) @@ -124,87 +131,147 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args) + } + } + + func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { + if !self.connected { + return SocketAckHandler(event: "fail") + } + + self.currentAck++ + let ackHandler = SocketAckHandler(event: event, ackNum: self.currentAck) + self.ackHandlers.append(ackHandler) + + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args, ack: true) + } + + return ackHandler + } + + private func _emit(event:String, _ args:[AnyObject], ack:Bool = false) { var frame:SocketEvent var str:String - var items = [AnyObject](count: args.count, repeatedValue: 1) - var numberOfPlaceholders = -1 - var hasBinary = false - var emitDatas = [NSData]() - for i in 0.. SocketAckHandler { + let ackHandler = SocketAckHandler(event: name) + let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) self.handlers.append(handler) + + return ackHandler } // Adds handler for multiple arg message - func onMultipleItems(name:String, callback:MultipleCallback) { - let handler = SocketEventHandler(event: name, callback: callback) + func onMultipleItems(name:String, callback:MultipleCallback) -> SocketAckHandler { + let ackHandler = SocketAckHandler(event: name) + let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) self.handlers.append(handler) + + return ackHandler } // Opens the connection to the socket @@ -296,6 +369,59 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return parsed } + private class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { + var items = [AnyObject](count: args.count, repeatedValue: 1) + var numberOfPlaceholders = -1 + var hasBinary = false + var emitDatas = [NSData]() + + for i in 0.. (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() @@ -354,7 +480,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Check for successful namepsace connect if self.nsp != nil { if stringMessage == "40/\(self.nsp!)" { - self.handleEvent(event: "connect", data: nil) + self.handleEvent("connect", data: nil) return } } @@ -384,17 +510,25 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { /** Begin check for message **/ - let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\[.*\\])?"].groups() + let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - if messageGroups[1] == "42" { + if messageGroups[1].hasPrefix("42") { + var mesNum = messageGroups[1] + var ackNum:String var namespace:String? var messagePart:String! - if messageGroups.count == 4 { - namespace = messageGroups[2] - messagePart = messageGroups[3] + if messageGroups[3] != "" { + ackNum = messageGroups[3] + } else { + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 2)) + mesNum.replaceRange(range, with: "") + ackNum = mesNum } + namespace = messageGroups[2] + messagePart = messageGroups[4] + if namespace == "" && self.nsp != nil { return } @@ -413,7 +547,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { - self.handleEvent(event: event, data: parsed) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } else if let strData = data { // There are multiple items in the message @@ -421,7 +561,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // parseData to try and get an array. let asArray = "[\(strData)]" if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - self.handleEvent(event: event, data: parsed, multipleItems: true) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -431,9 +577,34 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() if noItemMessage != nil && noItemMessage.count == 2 { let event = noItemMessage[1] - self.handleEvent(event: event, data: nil, multipleItems: false) + if ackNum == "" { + self.handleEvent(event, data: nil) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } + } else if messageGroups[1].hasPrefix("43") { + let arr = Array(messageGroups[1]) + var ackNum:String + let nsp = messageGroups[2] + + if nsp == "" && self.nsp != nil { + return + } + + if nsp == "" { + ackNum = String(arr[2...arr.count-1]) + } else { + ackNum = messageGroups[3] + } + + let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) + self.handleAck(ackNum.toInt()!, data: ackData) + + return } /** End Check for message @@ -463,23 +634,34 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { /** Begin check for binary placeholders **/ - let binaryGroup = mutMessage["(\\d*)-\\/?(\\w*)?,?\\[(\".*?\"),(.*)\\]$"].groups() + let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$"].groups() - if binaryGroup != nil { + if binaryGroup == nil { + return + } + + if binaryGroup[1].hasPrefix("45") { // println(binaryGroup) - var event:String! - var mutMessageObject:NSMutableString! + var ackNum:String + var event:String + var mutMessageObject:NSMutableString var namespace:String? + var numberOfPlaceholders:String let messageType = RegexMutable(binaryGroup[1]) - let numberOfPlaceholders = messageType["45"] ~= "" - // Check if message came from a namespace - if binaryGroup.count == 5 { - namespace = binaryGroup[2] - event = RegexMutable(binaryGroup[3])["\""] ~= "" - mutMessageObject = RegexMutable(binaryGroup[4]) + namespace = binaryGroup[2] + if binaryGroup[3] != "" { + ackNum = binaryGroup[3] as String + } else if self.nsp == nil && binaryGroup[2] != "" { + ackNum = binaryGroup[2] + } else { + ackNum = "" } + numberOfPlaceholders = (messageType["45"] ~= "") as String + event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String + mutMessageObject = RegexMutable(binaryGroup[5]) + if namespace == "" && self.nsp != nil { self.lastSocketMessage = nil return @@ -488,9 +670,40 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.integerValue) + var mes:SocketEvent + if ackNum == "" { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!) + } else { + self.currentAck = ackNum.toInt()! + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) + } + self.lastSocketMessage = mes + } else if binaryGroup[1].hasPrefix("46") { + let messageType = RegexMutable(binaryGroup[1]) + let numberOfPlaceholders = (messageType["46"] ~= "") as String + var ackNum:String + var nsp:String + + if binaryGroup[3] == "" { + ackNum = binaryGroup[2] + nsp = "" + } else { + ackNum = binaryGroup[3] + nsp = binaryGroup[2] + } + + if nsp == "" && self.nsp != nil { + return + } + var mutMessageObject = RegexMutable(binaryGroup[5]) + let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] + ~= "\"~~$2\"" + + self.lastSocketMessage = SocketEvent(event: "", args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) } /** End check for binary placeholders @@ -508,11 +721,32 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) - self.handleEvent(event: event, data: filledInArgs) + + if self.lastSocketMessage!.justAck! { + self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + return + } + + if self.lastSocketMessage!.ack != nil { + self.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + } else { + self.handleEvent(event, data: filledInArgs) + } } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() - self.handleEvent(event: event, data: filledInArgs, multipleItems: true) - return + + if self.lastSocketMessage!.justAck! { + self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + return + } + + if self.lastSocketMessage!.ack != nil { + self.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + } else { + self.handleEvent(event, data: filledInArgs) + } } } } @@ -525,8 +759,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Starts the ping timer private func startPingTimer(#interval:Int) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) + dispatch_async(dispatch_get_main_queue()) { + self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, + selector: Selector("sendPing"), userInfo: nil, repeats: true) + } } // We lost connection and should attempt to reestablish @@ -535,7 +771,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connecting = false self.reconnects = false self.reconnecting = false - self.handleEvent(event: "disconnect", data: "Failed to reconnect") + self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) return } else if self.connected { self.connecting = false @@ -544,7 +780,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent(event: "reconnectAttempt", data: triesLeft) + self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -567,7 +803,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - self.parseSocketMessage(message) + dispatch_async(self.handleQueue) {[weak self] in + if self == nil { + return + } + + self?.parseSocketMessage(message) + } } // Called when the socket is opened @@ -583,7 +825,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - self.handleEvent(event: "connect", data: nil) + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.handleEvent("connect", data: nil) } // Called when the socket is closed @@ -592,9 +836,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent(event: "disconnect", data: reason) + self.handleEvent("disconnect", data: reason, isInternalMessage: true) } else { - self.handleEvent(event: "reconnect", data: reason) + self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } @@ -605,11 +849,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.connected = false self.connecting = false - self.handleEvent(event: "error", data: error.localizedDescription) + self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent(event: "disconnect", data: error.localizedDescription) + self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) } else if !self.reconnecting { - self.handleEvent(event: "reconnect", data: error.localizedDescription) + self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 96c03cf..16527ab 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -13,7 +13,7 @@ import Foundation -var swiftRegexCache = Dictionary() +var swiftRegexCache = [String: NSRegularExpression]() public class SwiftRegex: NSObject, BooleanType { @@ -60,15 +60,15 @@ public class SwiftRegex: NSObject, BooleanType { } public func range(options: NSMatchingOptions = nil) -> NSRange { - return regex.rangeOfFirstMatchInString(target, options: nil, range: targetRange) + return regex.rangeOfFirstMatchInString(target as String, options: nil, range: targetRange) } public func match(options: NSMatchingOptions = nil) -> String! { - return substring(range(options: options)) + return substring(range(options: options)) as String } public func groups(options: NSMatchingOptions = nil) -> [String]! { - return groupsForMatch( regex.firstMatchInString(target, options: options, range: targetRange) ) + return groupsForMatch( regex.firstMatchInString(target as String, options: options, range: targetRange) ) } func groupsForMatch(match: NSTextCheckingResult!) -> [String]! { @@ -93,9 +93,9 @@ public class SwiftRegex: NSObject, BooleanType { } set(newValue) { if let mutableTarget = target as? NSMutableString { - for match in matchResults().reverse() { + for match in matchResults()!.reverse() { let replacement = regex.replacementStringForResult( match, - inString: target, offset: 0, template: newValue ) + inString: target as String, offset: 0, template: newValue ) mutableTarget.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) } } else { @@ -104,26 +104,33 @@ public class SwiftRegex: NSObject, BooleanType { } } - func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult] { - return regex.matchesInString(target, options: options, range: targetRange) as [NSTextCheckingResult] + func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult]? { + let matches = regex.matchesInString(target as String, options: options, range: targetRange) + as? [NSTextCheckingResult] + + if matches != nil { + return matches! + } else { + return nil + } } public func ranges(options: NSMatchingOptions = nil) -> [NSRange] { - return matchResults(options: options).map { $0.range } + return matchResults(options: options)!.map { $0.range } } public func matches(options: NSMatchingOptions = nil) -> [String] { - return matchResults(options: options).map { self.substring($0.range) } + return matchResults(options: options)!.map( { self.substring($0.range) as String } ) } public func allGroups(options: NSMatchingOptions = nil) -> [[String]] { - return matchResults(options: options).map { self.groupsForMatch($0) } + return matchResults(options: options)!.map { self.groupsForMatch($0) } } public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { var out = Dictionary() - for match in matchResults(options: options) { - out[substring(match.rangeAtIndex(1))] = + for match in matchResults(options: options)! { + out[substring(match.rangeAtIndex(1)) as String] = substring(match.rangeAtIndex(2)) } return out @@ -134,19 +141,19 @@ public class SwiftRegex: NSObject, BooleanType { let out = NSMutableString() var pos = 0 - regex.enumerateMatchesInString(target, options: options, range: targetRange ) { + regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) { (match: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer) in let matchRange = match.range - out.appendString( self.substring( NSRange(location:pos, length:matchRange.location-pos) ) ) + out.appendString( self.substring( NSRange(location:pos, length:matchRange.location-pos) ) as String ) out.appendString( substitution(match, stop) ) pos = matchRange.location + matchRange.length } - out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) ) + out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) as String ) if let mutableTarget = target as? NSMutableString { - mutableTarget.setString(out) + mutableTarget.setString(out as String) return mutableTarget } else { SwiftRegex.failure("Modify on non-mutable") @@ -208,43 +215,41 @@ extension String { } public func RegexMutable(string: NSString) -> NSMutableString { - return NSMutableString(string:string) + return NSMutableString(string:string as String) } public func ~= (left: SwiftRegex, right: String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop in return left.regex.replacementStringForResult( match, - inString: left.target, offset: 0, template: right ) - } + inString: left.target as String, offset: 0, template: right ) + }, options: nil) } public func ~= (left: SwiftRegex, right: [String]) -> NSMutableString { var matchNumber = 0 - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop -> String in if ++matchNumber == right.count { stop.memory = true } return left.regex.replacementStringForResult( match, - inString: left.target, offset: 0, template: right[matchNumber-1] ) - } + inString: left.target as String, offset: 0, template: right[matchNumber-1] ) + }, options: nil) } public func ~= (left: SwiftRegex, right: (String) -> String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in - return right(left.substring(match.range)) - } + // return right(left.substring(match.range)) + return left.substituteMatches( + {match, stop -> String in + right(left.substring(match.range) as String) + }, options: nil) } public func ~= (left: SwiftRegex, right: ([String]) -> String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop -> String in return right(left.groupsForMatch(match)) - } + }, options: nil) } // my take on custom threading operators from