diff --git a/.swift-version b/.swift-version index 4d54dad..bec3a35 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0.2 +system diff --git a/CHANGELOG.md b/CHANGELOG.md index 415de90..9c9bcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# 13.1.1 +# v13.1.2 + +- Fix [#950](https://github.com/socketio/socket.io-client-swift/issues/950) +- Conforming to `SocketEngineWebsocket` no longer requires conforming to `WebsocketDelegate` + + +# v13.1.1 - Fix [#923](https://github.com/socketio/socket.io-client-swift/issues/923) - Fix [#894](https://github.com/socketio/socket.io-client-swift/issues/894) diff --git a/Cartfile.resolved b/Cartfile.resolved index 8dff7a8..62b4094 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ +github "daltoniam/Starscream" "3.0.3" github "daltoniam/common-crypto-spm" "1.1.0" github "daltoniam/zlib-spm" "1.1.0" -github "daltoniam/Starscream" "3.0.2" diff --git a/Source/SocketIO/Ack/SocketAckEmitter.swift b/Source/SocketIO/Ack/SocketAckEmitter.swift index db8cecf..a7c80be 100644 --- a/Source/SocketIO/Ack/SocketAckEmitter.swift +++ b/Source/SocketIO/Ack/SocketAckEmitter.swift @@ -108,7 +108,7 @@ public final class OnAckCallback : NSObject { /// Completes an emitWithAck. If this isn't called, the emit never happens. /// - /// - parameter after: The number of seconds before this emit times out if an ack hasn't been received. + /// - parameter seconds: The number of seconds before this emit times out if an ack hasn't been received. /// - parameter callback: The callback called when an ack is received, or when a timeout happens. /// To check for timeout, use `SocketAckStatus`'s `noAck` case. @objc @@ -121,9 +121,9 @@ public final class OnAckCallback : NSObject { guard seconds != 0 else { return } socket.manager?.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {[weak socket] in - guard let socket = socket, let manager = socket.manager else { return } + guard let socket = socket else { return } - socket.ackHandlers.timeoutAck(self.ackNumber, onQueue: manager.handleQueue) + socket.ackHandlers.timeoutAck(self.ackNumber) } } diff --git a/Source/SocketIO/Ack/SocketAckManager.swift b/Source/SocketIO/Ack/SocketAckManager.swift index cda3271..4679f37 100644 --- a/Source/SocketIO/Ack/SocketAckManager.swift +++ b/Source/SocketIO/Ack/SocketAckManager.swift @@ -58,31 +58,20 @@ private struct SocketAck : Hashable { } } -struct SocketAckManager { +class SocketAckManager { private var acks = Set(minimumCapacity: 1) - private let ackSemaphore = DispatchSemaphore(value: 1) - mutating func addAck(_ ack: Int, callback: @escaping AckCallback) { + func addAck(_ ack: Int, callback: @escaping AckCallback) { acks.insert(SocketAck(ack: ack, callback: callback)) } /// Should be called on handle queue - mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { - ackSemaphore.wait() - defer { ackSemaphore.signal() } - let ack = acks.remove(SocketAck(ack: ack)) - - onQueue.async() { ack?.callback(items) } + func executeAck(_ ack: Int, with items: [Any]) { + acks.remove(SocketAck(ack: ack))?.callback(items) } /// Should be called on handle queue - mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { - ackSemaphore.wait() - defer { ackSemaphore.signal() } - let ack = acks.remove(SocketAck(ack: ack)) - - onQueue.async() { - ack?.callback?([SocketAckStatus.noAck.rawValue]) - } + func timeoutAck(_ ack: Int) { + acks.remove(SocketAck(ack: ack))?.callback?([SocketAckStatus.noAck.rawValue]) } } diff --git a/Source/SocketIO/Client/SocketIOClient.swift b/Source/SocketIO/Client/SocketIOClient.swift index 587319c..a8f12f4 100644 --- a/Source/SocketIO/Client/SocketIOClient.swift +++ b/Source/SocketIO/Client/SocketIOClient.swift @@ -75,7 +75,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { } } - var ackHandlers = SocketAckManager() + let ackHandlers = SocketAckManager() private(set) var currentAck = -1 @@ -86,7 +86,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// Type safe way to create a new SocketIOClient. `opts` can be omitted. /// /// - parameter manager: The manager for this socket. - /// - parameter socketURL: The url of the socket.io server. + /// - parameter nsp: The namespace of the socket. @objc public init(manager: SocketManagerSpec, nsp: String) { self.manager = manager @@ -115,7 +115,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection /// has failed. Pass 0 to never timeout. - /// - parameter withHandler: The handler to call when the client fails to connect. + /// - parameter handler: The handler to call when the client fails to connect. @objc open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) { assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") @@ -213,7 +213,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// Same as emit, but meant for Objective-C /// /// - parameter event: The event to send. - /// - parameter with: The items to send with this event. Send an empty array to send no data. + /// - parameter items: The items to send with this event. Send an empty array to send no data. @objc open func emit(_ event: String, with items: [Any]) { guard status == .connected else { @@ -270,7 +270,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// ``` /// /// - parameter event: The event to send. - /// - parameter with: The items to send with this event. Use `[]` to send nothing. + /// - parameter items: The items to send with this event. Use `[]` to send nothing. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent. @objc open func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { @@ -314,11 +314,11 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// - parameter data: The data sent back with this ack. @objc open func handleAck(_ ack: Int, data: [Any]) { - guard status == .connected, let manager = self.manager else { return } + guard status == .connected else { return } DefaultSocketLogger.Logger.log("Handling ack: \(ack) with data: \(data)", type: logType) - ackHandlers.executeAck(ack, with: data, onQueue: manager.handleQueue) + ackHandlers.executeAck(ack, with: data) } /// Called on socket.io specific events. @@ -334,7 +334,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// - parameter event: The name of the event. /// - parameter data: The data that was sent with this event. /// - parameter isInternalMessage: Whether this event was sent internally. If `true` it is always sent to handlers. - /// - parameter withAck: If > 0 then this event expects to get an ack back from the client. + /// - parameter ack: If > 0 then this event expects to get an ack back from the client. @objc open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { guard status == .connected || isInternalMessage else { return } diff --git a/Source/SocketIO/Client/SocketIOClientSpec.swift b/Source/SocketIO/Client/SocketIOClientSpec.swift index 26e8e60..739c7e9 100644 --- a/Source/SocketIO/Client/SocketIOClientSpec.swift +++ b/Source/SocketIO/Client/SocketIOClientSpec.swift @@ -59,7 +59,7 @@ public protocol SocketIOClientSpec : class { /// /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection /// has failed. Pass 0 to never timeout. - /// - parameter withHandler: The handler to call when the client fails to connect. + /// - parameter handler: The handler to call when the client fails to connect. func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) /// Called when the client connects to a namespace. If the client was created with a namespace upfront, @@ -134,7 +134,7 @@ public protocol SocketIOClientSpec : class { /// - parameter event: The name of the event. /// - parameter data: The data that was sent with this event. /// - parameter isInternalMessage: Whether this event was sent internally. If `true` it is always sent to handlers. - /// - parameter withAck: If > 0 then this event expects to get an ack back from the client. + /// - parameter ack: If > 0 then this event expects to get an ack back from the client. func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) /// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the diff --git a/Source/SocketIO/Engine/SocketEngine.swift b/Source/SocketIO/Engine/SocketEngine.swift index 6b6df7a..fc1c1ad 100644 --- a/Source/SocketIO/Engine/SocketEngine.swift +++ b/Source/SocketIO/Engine/SocketEngine.swift @@ -280,7 +280,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } private func createWebSocketAndConnect() { - ws?.delegate = nil // TODO this seems a bit defensive, is this really needed? var req = URLRequest(url: urlWebSocketWithSid) addHeaders(to: &req) @@ -288,10 +287,33 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll ws = WebSocket(request: req) ws?.callbackQueue = engineQueue ws?.enableCompression = compress - ws?.delegate = self ws?.disableSSLCertValidation = selfSigned ws?.security = security?.security + ws?.onConnect = {[weak self] in + guard let this = self else { return } + + this.websocketDidConnect() + } + + ws?.onDisconnect = {[weak self] error in + guard let this = self else { return } + + this.websocketDidDisconnect(error: error) + } + + ws?.onData = {[weak self] data in + guard let this = self else { return } + + this.parseEngineData(data) + } + + ws?.onText = {[weak self] message in + guard let this = self else { return } + + this.parseEngineMessage(message) + } + ws?.connect() } @@ -461,8 +483,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// Parses a raw engine.io packet. /// /// - parameter message: The message to parse. - /// - parameter fromPolling: Whether this message is from long-polling. - /// If `true` we might have to fix utf8 encoding. public func parseEngineMessage(_ message: String) { DefaultSocketLogger.Logger.log("Got message: \(message)", type: SocketEngine.logType) @@ -586,8 +606,8 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// Writes a message to engine.io, independent of transport. /// /// - parameter msg: The message to send. - /// - parameter withType: The type of this message. - /// - parameter withData: Any data that this message has. + /// - parameter type: The type of this message. + /// - parameter data: Any data that this message has. public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { engineQueue.async { guard self.connected else { return } @@ -609,10 +629,9 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } } - // MARK: Starscream delegate conformance + // WebSocket Methods - /// Delegate method for connection. - public func websocketDidConnect(socket: WebSocketClient) { + private func websocketDidConnect() { if !forceWebsockets { probing = true probeWebSocket() @@ -623,8 +642,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } } - /// Delegate method for disconnection. - public func websocketDidDisconnect(socket: WebSocketClient, error: Error?) { + private func websocketDidDisconnect(error: Error?) { probing = false if closed { diff --git a/Source/SocketIO/Engine/SocketEngineSpec.swift b/Source/SocketIO/Engine/SocketEngineSpec.swift index 59ba6b6..fa08519 100644 --- a/Source/SocketIO/Engine/SocketEngineSpec.swift +++ b/Source/SocketIO/Engine/SocketEngineSpec.swift @@ -130,15 +130,13 @@ import Starscream /// Parses a raw engine.io packet. /// /// - parameter message: The message to parse. - /// - parameter fromPolling: Whether this message is from long-polling. - /// If `true` we might have to fix utf8 encoding. func parseEngineMessage(_ message: String) /// Writes a message to engine.io, independent of transport. /// /// - parameter msg: The message to send. - /// - parameter withType: The type of this message. - /// - parameter withData: Any data that this message has. + /// - parameter type: The type of this message. + /// - parameter data: Any data that this message has. func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) } diff --git a/Source/SocketIO/Engine/SocketEngineWebsocket.swift b/Source/SocketIO/Engine/SocketEngineWebsocket.swift index 97a4a72..6dc11ac 100644 --- a/Source/SocketIO/Engine/SocketEngineWebsocket.swift +++ b/Source/SocketIO/Engine/SocketEngineWebsocket.swift @@ -27,7 +27,7 @@ import Foundation import Starscream /// Protocol that is used to implement socket.io WebSocket support -public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { +public protocol SocketEngineWebsocket : SocketEngineSpec { // MARK: Methods /// Sends an engine.io message through the WebSocket transport. @@ -66,16 +66,4 @@ extension SocketEngineWebsocket { } } } - - // MARK: Starscream delegate methods - - /// Delegate method for when a message is received. - public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) { - parseEngineMessage(text) - } - - /// Delegate method for when binary is received. - public func websocketDidReceiveData(socket: WebSocketClient, data: Data) { - parseEngineData(data) - } } diff --git a/Source/SocketIO/Manager/SocketManager.swift b/Source/SocketIO/Manager/SocketManager.swift index 9d04d1e..e4fbf7d 100644 --- a/Source/SocketIO/Manager/SocketManager.swift +++ b/Source/SocketIO/Manager/SocketManager.swift @@ -241,7 +241,7 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa /// This will remove the socket for the manager's control, and make the socket instance useless and ready for /// releasing. /// - /// - parameter forNamespace: The namespace to disconnect from. + /// - parameter nsp: The namespace to disconnect from. open func disconnectSocket(forNamespace nsp: String) { guard let socket = nsps.removeValue(forKey: nsp) else { DefaultSocketLogger.Logger.log("Could not find socket for \(nsp) to disconnect", @@ -282,7 +282,7 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa /// Same as `emitAll(_:_:)`, but meant for Objective-C. /// /// - parameter event: The event to send. - /// - parameter withItems: The data to send with this event. + /// - parameter items: The data to send with this event. open func emitAll(_ event: String, withItems items: [Any]) { forAll {socket in socket.emit(event, with: items) @@ -508,7 +508,7 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa /// Call one of the `disconnectSocket` methods on this class to remove the socket from manager control. /// Or call `SocketIOClient.disconnect()` on the client. /// - /// - parameter forNamespace: The namespace for the socket. + /// - parameter nsp: The namespace for the socket. /// - returns: A `SocketIOClient` for the given namespace. open func socket(forNamespace nsp: String) -> SocketIOClient { assert(nsp.hasPrefix("/"), "forNamespace must have a leading /") diff --git a/Source/SocketIO/Manager/SocketManagerSpec.swift b/Source/SocketIO/Manager/SocketManagerSpec.swift index 52bdcab..79f0e98 100644 --- a/Source/SocketIO/Manager/SocketManagerSpec.swift +++ b/Source/SocketIO/Manager/SocketManagerSpec.swift @@ -103,13 +103,13 @@ public protocol SocketManagerSpec : class, SocketEngineClient { /// Disconnects the socket associated with `forNamespace`. /// - /// - parameter forNamespace: The namespace to disconnect from. + /// - parameter nsp: The namespace to disconnect from. func disconnectSocket(forNamespace nsp: String) /// Sends an event to the server on all namespaces in this manager. /// /// - parameter event: The event to send. - /// - parameter withItems: The data to send with this event. + /// - parameter items: The data to send with this event. func emitAll(_ event: String, withItems items: [Any]) /// Tries to reconnect to the server. @@ -133,7 +133,7 @@ public protocol SocketManagerSpec : class, SocketEngineClient { /// Call one of the `disconnectSocket` methods on the implementing class to remove the socket from manager control. /// Or call `SocketIOClient.disconnect()` on the client. /// - /// - parameter forNamespace: The namespace for the socket. + /// - parameter nsp: The namespace for the socket. /// - returns: A `SocketIOClient` for the given namespace. func socket(forNamespace nsp: String) -> SocketIOClient } diff --git a/Tests/TestSocketIO/SocketAckManagerTest.swift b/Tests/TestSocketIO/SocketAckManagerTest.swift index 455c5b9..b4f7b5d 100644 --- a/Tests/TestSocketIO/SocketAckManagerTest.swift +++ b/Tests/TestSocketIO/SocketAckManagerTest.swift @@ -21,7 +21,7 @@ class SocketAckManagerTest : XCTestCase { } ackManager.addAck(1, callback: callback) - ackManager.executeAck(1, with: itemsArray, onQueue: DispatchQueue.main) + ackManager.executeAck(1, with: itemsArray) waitForExpectations(timeout: 3.0, handler: nil) } @@ -44,7 +44,7 @@ class SocketAckManagerTest : XCTestCase { } ackManager.addAck(1, callback: callback) - ackManager.timeoutAck(1, onQueue: DispatchQueue.main) + ackManager.timeoutAck(1) waitForExpectations(timeout: 0.2, handler: nil) } diff --git a/Tests/TestSocketIO/SocketSideEffectTest.swift b/Tests/TestSocketIO/SocketSideEffectTest.swift index 156fe4d..5c8a77d 100644 --- a/Tests/TestSocketIO/SocketSideEffectTest.swift +++ b/Tests/TestSocketIO/SocketSideEffectTest.swift @@ -38,6 +38,19 @@ class SocketSideEffectTest: XCTestCase { waitForExpectations(timeout: 3, handler: nil) } + func testHandleAckWithAckEmit() { + let expect = expectation(description: "handled ack") + socket.emitWithAck("test").timingOut(after: 0) {data in + XCTAssertEqual(data[0] as? String, "hello world") + + self.socket.emitWithAck("test").timingOut(after: 0) {data in} + expect.fulfill() + } + + manager.parseEngineMessage("30[\"hello world\"]") + waitForExpectations(timeout: 3, handler: nil) + } + func testHandleAck2() { let expect = expectation(description: "handled ack2") socket.emitWithAck("test").timingOut(after: 0) {data in