diff --git a/README.md b/README.md index b5ef8be..1cb7717 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:@"ackack" withItems:@[@1]](10, ^(NSArray* data) { + NSLog(@"Got ack"); + }); }]; ``` 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 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 52c6091..304ddd8 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -175,6 +175,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { self._websocket = true self._polling = false self.fastUpgrade = false + self.probing = false self.flushProbeWait() } @@ -229,7 +230,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 +241,10 @@ public class SocketEngine: NSObject, WebSocketDelegate { } self?.probeWait.removeAll(keepCapacity: false) + + if self?.postWait.count != 0 { + self?.flushWaitingForPostToWebSocket() + } } } @@ -269,11 +274,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 +292,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() } @@ -443,7 +455,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.upgradeTransport() return } - + return } else if type == PacketType.OPEN.rawValue { var err:NSError? @@ -518,6 +530,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)") @@ -539,6 +553,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)") @@ -570,9 +586,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) } } 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 0681b8f..84763cf 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