From affd7923342ffa1e6937f6090c0878d7a5774384 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 3 Apr 2015 16:04:50 -0400 Subject: [PATCH 1/4] redo acks --- README.md | 8 +- ...ketAckHandler.swift => SocketAckMap.swift} | 66 ++++----- SwiftIO/SocketEngine.swift | 41 ++++-- SwiftIO/SocketIOClient.swift | 139 +++++++++--------- SwiftIO/SocketPacket.swift | 2 +- 5 files changed, 130 insertions(+), 126 deletions(-) rename SwiftIO/{SocketAckHandler.swift => SocketAckMap.swift} (52%) diff --git a/README.md b/README.md index b5ef8be..2868658 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ socket.on("connect") {data, ack in socket.on("currentAmount") {data, ack in if let cur = data?[0] as? Double { - socket.emitWithAck("canUpdate", cur).onAck(0) {data in + socket.emitWithAck("canUpdate", cur)(timeout: 0) {data in socket.emit("update", ["amount": cur + 2.50]) } @@ -30,9 +30,9 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8 [socket on: @"connect" callback: ^(NSArray* data, void (^ack)(NSArray*)) { NSLog(@"connected"); [socket emitObjc:@"echo" withItems:@[@"echo test"]]; - [[socket emitWithAckObjc:@"ackack" withItems:@[@"test"]] onAck:0 withCallback:^(NSArray* data) { - NSLog(@"Got data"); - }]; + [socket emitWithAckObjc:@"connect" withItems:@[@1]](10, ^(NSArray* data) { + NSLog(@"Got ack"); + }); }]; ``` diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckMap.swift similarity index 52% rename from SwiftIO/SocketAckHandler.swift rename to SwiftIO/SocketAckMap.swift index 664fc7e..041b4f7 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckMap.swift @@ -1,9 +1,9 @@ // -// SocketAckHandler.swift -// Socket.IO-Swift +// SocketAckMap.swift +// SocketIO-Swift +// +// Created by Erik Little on 4/3/15. // -// 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 @@ -25,45 +25,35 @@ import Foundation public typealias AckCallback = (NSArray?) -> Void +public typealias OnAckCallback = (timeout:UInt64, callback:AckCallback) -> Void -@objc public class SocketAckHandler { - let ackNum:Int! - let event:String! - var acked = false - var callback:AckCallback? - weak var socket:SocketIOClient? - +struct SocketAckMap { + private var acks = [Int: AckCallback]() + private var waiting = [Int: Bool]() - init(event:String, ackNum:Int = 0, socket:SocketIOClient) { - self.ackNum = ackNum - self.event = event - self.socket = socket + mutating func addAck(ack:Int, callback:AckCallback) { + waiting[ack] = true + acks[ack] = callback } - public func onAck(timeout:UInt64, withCallback callback:AckCallback) { - self.callback = callback + mutating func executeAck(ack:Int, items:[AnyObject]?) { + let callback = acks[ack] + waiting[ack] = false - - if timeout != 0 { - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) - dispatch_after(time, dispatch_get_main_queue()) {[weak self] in - if self == nil { - return - } - - if !self!.acked { - self?.executeAck(["No ACK"]) - self?.socket?.removeAck(self!) - } - } - } - } - - func executeAck(data:NSArray?) { - dispatch_async(dispatch_get_main_queue()) {[weak self, cb = self.callback] in - self?.acked = true - cb?(data) + dispatch_async(dispatch_get_main_queue()) { + callback?(items) return } + + acks.removeValueForKey(ack) } -} \ No newline at end of file + + mutating func timeoutAck(ack:Int) { + if waiting[ack] != nil && waiting[ack]! { + acks[ack]?(["NO ACK"]) + } + + acks.removeValueForKey(ack) + waiting.removeValueForKey(ack) + } +} diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 2e66584..3f28d4a 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -164,18 +164,21 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.ws = WebSocket(url: NSURL(string: self.urlWebSocket! + "&sid=\(self.sid)")!) self.ws?.queue = self.handleQueue self.ws?.delegate = self - + if connect { self.ws?.connect() } } private func doFastUpgrade() { - self.sendWebSocketMessage("", withType: PacketType.UPGRADE) - self._websocket = true - self._polling = false - self.fastUpgrade = false - self.flushProbeWait() + dispatch_async(self.emitQueue) {[weak self] in + self?.sendWebSocketMessage("", withType: PacketType.UPGRADE) + self?._websocket = true + self?._polling = false + self?.fastUpgrade = false + self?.probing = false + self?.flushProbeWait() + } } private func doPoll() { @@ -229,7 +232,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { } private func flushProbeWait() { - // println("flushing probe wait") + // NSLog("flushing probe wait") dispatch_async(self.emitQueue) {[weak self] in if self == nil { return @@ -240,6 +243,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { } self?.probeWait.removeAll(keepCapacity: false) + // NSLog("waiting for post after flush probe: \(self!.postWait.count)") } } @@ -269,11 +273,14 @@ public class SocketEngine: NSObject, WebSocketDelegate { let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - // NSLog("posting: \(postStr)") req.HTTPBody = postData req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") self.waitingForPost = true + + // NSLog("posting: \(postStr)") + // NSLog("Posting with WS status of: \(self.websocket)") + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return @@ -284,9 +291,13 @@ public class SocketEngine: NSObject, WebSocketDelegate { self?.waitingForPost = false dispatch_async(self!.emitQueue) { - self?.flushWaitingForPost() - self?.doPoll() - return + if self!.fastUpgrade { + self?.doFastUpgrade() + return + } else { + self?.flushWaitingForPost() + self?.doPoll() + } }}.resume() } @@ -517,6 +528,8 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.write("", withType: PacketType.PING, withData: nil) } + /// Send polling message. + /// Only call on emitQueue private func sendPollMessage(var msg:String, withType type:PacketType, datas:ContiguousArray? = nil) { // println("Sending poll: \(msg) as type: \(type.rawValue)") @@ -538,6 +551,8 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } + /// Send message on WebSockets + /// Only call on emitQueue private func sendWebSocketMessage(str:String, withType type:PacketType, datas:ContiguousArray? = nil) { // println("Sending ws: \(str) as type: \(type.rawValue)") @@ -569,9 +584,9 @@ public class SocketEngine: NSObject, WebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + // NSLog("Doing fast upgrade") // Do a fast upgrade self.fastUpgrade = true - self.probing = false self.sendPollMessage("", withType: PacketType.NOOP) } } @@ -591,7 +606,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + // Delagate methods public func websocketDidConnect(socket:WebSocket) { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 7ade9cc..b593ac8 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -25,9 +25,7 @@ import Foundation public class SocketIOClient: NSObject, SocketEngineClient { - let reconnectAttempts:Int! private lazy var params = [String: AnyObject]() - private var ackHandlers = ContiguousArray() private var anyHandler:((SocketAnyEvent) -> Void)? private var _closed = false private var _connected = false @@ -42,14 +40,15 @@ public class SocketIOClient: NSObject, SocketEngineClient { private var _reconnecting = false private var reconnectTimer:NSTimer? + let reconnectAttempts:Int! + var ackHandlers = SocketAckMap() var currentAck = -1 var waitingData = ContiguousArray() public let socketURL:String - public let handleQueue = dispatch_queue_create("handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), - DISPATCH_QUEUE_SERIAL) - public let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), - DISPATCH_QUEUE_SERIAL) + public let handleAckQueue = dispatch_queue_create("handleAckQueue", DISPATCH_QUEUE_SERIAL) + public let handleQueue = dispatch_queue_create("handleQueue", DISPATCH_QUEUE_SERIAL) + public let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL) public var closed:Bool { return self._closed } @@ -89,6 +88,26 @@ public class SocketIOClient: NSObject, SocketEngineClient { // Set options if opts != nil { + if let cookies = opts!["cookies"] as? [NSHTTPCookie] { + self.cookies = cookies + } + + if let polling = opts!["forcePolling"] as? Bool { + self.forcePolling = polling + } + + if let ws = opts!["forceWebsockets"] as? Bool { + self.forceWebsockets = ws + } + + if var nsp = opts!["nsp"] as? String { + if nsp != "/" && nsp.hasPrefix("/") { + nsp.removeAtIndex(nsp.startIndex) + } + + self.nsp = nsp + } + if let reconnects = opts!["reconnects"] as? Bool { self.reconnects = reconnects } @@ -102,26 +121,6 @@ public class SocketIOClient: NSObject, SocketEngineClient { if let reconnectWait = opts!["reconnectWait"] as? Int { self.reconnectWait = abs(reconnectWait) } - - if var nsp = opts!["nsp"] as? String { - if nsp != "/" && nsp.hasPrefix("/") { - nsp.removeAtIndex(nsp.startIndex) - } - - self.nsp = nsp - } - - if let polling = opts!["forcePolling"] as? Bool { - self.forcePolling = polling - } - - if let ws = opts!["forceWebsockets"] as? Bool { - self.forceWebsockets = ws - } - - if let cookies = opts!["cookies"] as? [NSHTTPCookie] { - self.cookies = cookies - } } else { self.reconnectAttempts = -1 } @@ -190,6 +189,29 @@ public class SocketIOClient: NSObject, SocketEngineClient { self.engine?.open(opts: params) } + private func createOnAck(event:String, items:[AnyObject]) -> OnAckCallback { + return {[weak self, ack = ++self.currentAck] timeout, callback in + if self == nil { + return + } + + self?.ackHandlers.addAck(ack, callback) + + dispatch_async(self!.emitQueue) { + self?._emit(event, items, ack: ack) + return + } + + if timeout != 0 { + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) + dispatch_after(time, dispatch_get_main_queue()) { + self?.ackHandlers.timeoutAck(ack) + return + } + } + } + } + func didConnect() { self._closed = false self._connected = true @@ -219,6 +241,13 @@ public class SocketIOClient: NSObject, SocketEngineClient { self.handleEvent("disconnect", data: [reason], isInternalMessage: true) } + /** + Same as close + */ + public func disconnect(#fast:Bool) { + self.close(fast: fast) + } + /** Send a message to the server */ @@ -251,43 +280,23 @@ public class SocketIOClient: NSObject, SocketEngineClient { Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add an ack. */ - public func emitWithAck(event:String, _ items:AnyObject...) -> SocketAckHandler { + public func emitWithAck(event:String, _ items:AnyObject...) -> OnAckCallback { if !self.connected { - return SocketAckHandler(event: "fail", socket: self) + return createOnAck(event, items: items) } - self.currentAck++ - let ackHandler = SocketAckHandler(event: event, - ackNum: self.currentAck, socket: self) - self.ackHandlers.append(ackHandler) - - dispatch_async(self.emitQueue) {[weak self, ack = self.currentAck] in - self?._emit(event, items, ack: ack) - return - } - - return ackHandler + return self.createOnAck(event, items: items) } /** Same as emitWithAck, but for Objective-C */ - public func emitWithAckObjc(event:String, withItems items:[AnyObject]) -> SocketAckHandler { + public func emitWithAckObjc(event:String, withItems items:[AnyObject]) -> OnAckCallback { if !self.connected { - return SocketAckHandler(event: "fail", socket: self) + return self.createOnAck(event, items: items) } - self.currentAck++ - let ackHandler = SocketAckHandler(event: event, - ackNum: self.currentAck, socket: self) - self.ackHandlers.append(ackHandler) - - dispatch_async(self.emitQueue) {[weak self, ack = self.currentAck] in - self?._emit(event, items, ack: ack) - return - } - - return ackHandler + return self.createOnAck(event, items: items) } private func _emit(event:String, _ args:[AnyObject], ack:Int? = nil) { @@ -331,21 +340,15 @@ public class SocketIOClient: NSObject, SocketEngineClient { // Called when the socket gets an ack for something it sent func handleAck(ack:Int, data:AnyObject?) { - self.ackHandlers = self.ackHandlers.filter {handler in - if handler.ackNum != ack { - return true - } else { - if data is NSArray { - handler.executeAck(data as? NSArray) - } else if data != nil { - handler.executeAck([data!]) - } else { - handler.executeAck(nil) - } - - return false - } + var ackData:[AnyObject]? + + if data is NSArray { + ackData = data as? NSArray + } else if data != nil { + ackData = [data!] } + + self.ackHandlers.executeAck(ack, items: ackData) } /** @@ -428,10 +431,6 @@ public class SocketIOClient: NSObject, SocketEngineClient { } } - func removeAck(ack:SocketAckHandler) { - self.ackHandlers = self.ackHandlers.filter {$0 === ack ? false : true} - } - // We lost connection and should attempt to reestablish func tryReconnect() { if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { diff --git a/SwiftIO/SocketPacket.swift b/SwiftIO/SocketPacket.swift index 997e032..e812b4b 100644 --- a/SwiftIO/SocketPacket.swift +++ b/SwiftIO/SocketPacket.swift @@ -24,7 +24,7 @@ import Foundation -enum SocketPacketType: Int { +enum SocketPacketType:Int { case CONNECT = 0 case DISCONNECT = 1 case EVENT = 2 From d4470a908449c175f328806620c3ca4c889a7e2d Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 3 Apr 2015 16:05:47 -0400 Subject: [PATCH 2/4] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2868658..1cb7717 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8 [socket on: @"connect" callback: ^(NSArray* data, void (^ack)(NSArray*)) { NSLog(@"connected"); [socket emitObjc:@"echo" withItems:@[@"echo test"]]; - [socket emitWithAckObjc:@"connect" withItems:@[@1]](10, ^(NSArray* data) { + [socket emitWithAckObjc:@"ackack" withItems:@[@1]](10, ^(NSArray* data) { NSLog(@"Got ack"); }); }]; From 838dade0fa2294acce9441ce70dcbb9c5f6ea6a4 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 3 Apr 2015 16:18:15 -0400 Subject: [PATCH 3/4] Fix messages getting stuck in post --- SwiftIO/SocketEngine.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 3f28d4a..dae5a05 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -171,14 +171,12 @@ public class SocketEngine: NSObject, WebSocketDelegate { } private func doFastUpgrade() { - dispatch_async(self.emitQueue) {[weak self] in - self?.sendWebSocketMessage("", withType: PacketType.UPGRADE) - self?._websocket = true - self?._polling = false - self?.fastUpgrade = false - self?.probing = false - self?.flushProbeWait() - } + self.sendWebSocketMessage("", withType: PacketType.UPGRADE) + self._websocket = true + self._polling = false + self.fastUpgrade = false + self.probing = false + self.flushProbeWait() } private func doPoll() { @@ -243,7 +241,10 @@ public class SocketEngine: NSObject, WebSocketDelegate { } self?.probeWait.removeAll(keepCapacity: false) - // NSLog("waiting for post after flush probe: \(self!.postWait.count)") + + if self?.postWait.count != 0 { + self?.flushWaitingForPostToWebSocket() + } } } From a1f24ff2817865cf8d5b11180eaaacf8683b244c Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 3 Apr 2015 16:19:32 -0400 Subject: [PATCH 4/4] bump version --- Socket.IO-Client-Swift.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index f1c301d..1c2d309 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Socket.IO-Client-Swift" - s.version = "1.4.4" + s.version = "1.5.0" s.summary = "Socket.IO-client for Swift" s.description = <<-DESC Socket.IO-client for Swift. @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.author = { "Erik" => "nuclear.ace@gmail.com" } s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.4.4' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.5.0' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files