From 216d8cae1e536a253d47f4233800d0ddcd8f6a86 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 14 Feb 2015 17:36:34 -0500 Subject: [PATCH] Add acks, still needs more testing --- SwiftIO/SocketAckHandler.swift | 45 +++++ SwiftIO/SocketEvent.swift | 83 ++++++-- SwiftIO/SocketEventHandler.swift | 7 +- SwiftIO/SocketIOClient.swift | 333 +++++++++++++++++++++---------- 4 files changed, 337 insertions(+), 131 deletions(-) create mode 100644 SwiftIO/SocketAckHandler.swift diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift new file mode 100644 index 0000000..c6bed5a --- /dev/null +++ b/SwiftIO/SocketAckHandler.swift @@ -0,0 +1,45 @@ +// +// 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 event:String! + var ackData:[AnyObject]? + var callback:AckCallback? + + init(event:String) { + 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 729b959..b694de2 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -25,16 +25,18 @@ import Foundation class SocketEvent { + 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, ack:Int? = nil) { self.event = event self.args = args self.placeholders = placeholders + self.ack = ack } func addData(data:NSData) -> Bool { @@ -61,7 +63,7 @@ class SocketEvent { } } - class func createMessageForEvent(event:String, withArgs args:[AnyObject], + static func createMessageForEvent(event:String, withArgs args:[AnyObject], hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?) -> String { var message:String @@ -69,39 +71,78 @@ class SocketEvent { if !hasBinary { if nsp == nil { - message = "42[\"\(event)\"" + message = "42[\"\(event)\"," } else { - message = "42/\(nsp!),[\"\(event)\"" + message = "42/\(nsp!),[\"\(event)\"," } } else { if nsp == nil { - message = "45\(datas)-[\"\(event)\"" + message = "45\(datas)-[\"\(event)\"," } else { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + message = "45\(datas)-/\(nsp!),[\"\(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) + } + + static 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! as! String - continue + return self.completeMessage(msg, args: args) + + } else { + msg = "43\(nsp)[" + + return self.completeMessage(msg, args: args) } - - if arg is String { - message += "\"\(arg)\"" - continue + } else { + if nsp == "/" { + msg = "46\(binary)-\(ack)[\"\(event)\"" + + return self.completeMessage(msg, args: args) + + } else { + msg = "46\(binary)-\(nsp),\(ack)[\"\(event)\"" + + return self.completeMessage(msg, args: args) } + } + } + + private static 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 782396e..a13add6 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,22 +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 45e5582..2fdf57b 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -29,10 +29,13 @@ typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { 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 secure = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? @@ -123,65 +126,30 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Sends a message with multiple args // If a message contains binary we have to send those // seperately. - func emit(event:String, _ args:AnyObject...) { + func emit(event:String, _ args:AnyObject...) -> SocketAckHandler { if !self.connected { - return + return SocketAckHandler(event: "fail") } - dispatch_async(self.emitQueue) {self._emit(event, args)} + let ackHandler = SocketAckHandler(event: event) + self.ackHandlers.append(ackHandler) + + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args) + } + + return ackHandler } private func _emit(event:String, _ args:[AnyObject]) { 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 @@ -294,7 +309,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses data for events - class func parseData(data:String?) -> AnyObject? { + static func parseData(data:String?) -> AnyObject? { if data == nil { return nil } @@ -312,8 +327,61 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return parsed } + private static 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]) { + private static func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false if placeholders == -1 { @@ -400,17 +468,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 } @@ -429,7 +505,12 @@ 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, data: parsed) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + 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 @@ -437,7 +518,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // parseData to try and get an array. let asArray = "[\(strData)]" if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - self.handleEvent(event, data: parsed, multipleItems: true) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -447,7 +533,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() if noItemMessage != nil && noItemMessage.count == 2 { let event = noItemMessage[1] - self.handleEvent(event, data: nil, multipleItems: false) + if ackNum == "" { + self.handleEvent(event, data: nil) + } else { + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -479,23 +570,30 @@ 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 { // 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])["\""] ~= "") as String - 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 @@ -504,8 +602,15 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.integerValue) + let mes:SocketEvent + if ackNum != "" { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!) + } else { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ack: ackNum.toInt()) + } + self.lastSocketMessage = mes } /** @@ -524,11 +629,22 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) - self.handleEvent(event, data: filledInArgs) + + 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, data: filledInArgs, multipleItems: true) - 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) + } } } } @@ -541,8 +657,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 @@ -551,8 +669,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connecting = false self.reconnects = false self.reconnecting = false - self.handleEvent("disconnect", data: "Failed to reconnect", - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) return } else if self.connected { self.connecting = false @@ -561,8 +678,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -585,7 +701,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - dispatch_async(self.handleQueue) {self.parseSocketMessage(message)} + dispatch_async(self.handleQueue) {[weak self] in + if self == nil { + return + } + + self?.parseSocketMessage(message) + } } // Called when the socket is opened @@ -612,11 +734,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: reason, - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: reason, isInternalMessage: true) } else { - self.handleEvent("reconnect", data: reason, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } @@ -627,14 +747,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.connected = false self.connecting = false - self.handleEvent("error", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) } else if !self.reconnecting { - self.handleEvent("reconnect", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } }