diff --git a/CHANGELOG.md b/CHANGELOG.md index 99aaf86..826a3d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v13.1.0 + +- Allow setting `SocketEngineSpec.extraHeaders` after init. +- Deprecate `SocketEngineSpec.websocket` in favor of just using the `SocketEngineSpec.polling` property. +- Enable bitcode for most platforms. +- Fix [#882](https://github.com/socketio/socket.io-client-swift/issues/882). This adds a new method +`SocketManger.removeSocket(_:)` that should be called if when you no longer wish to use a socket again. +This will cause the engine to no longer keep a strong reference to the socket and no longer track it. + # v13.0.1 - Fix not setting handleQueue on `SocketManager` diff --git a/README.md b/README.md index 391d8ba..64696e9 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ let package = Package( .executable(name: "socket.io-test", targets: ["YourTargetName"]) ], dependencies: [ - .package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMinor(from: "13.0.0")) + .package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMinor(from: "13.1.0")) ], targets: [ .target(name: "YourTargetName", dependencies: ["SocketIO"], path: "./Path/To/Your/Sources") @@ -99,7 +99,7 @@ Then import `import SocketIO`. ### Carthage Add this line to your `Cartfile`: ``` -github "socketio/socket.io-client-swift" ~> 13.0.0 +github "socketio/socket.io-client-swift" ~> 13.1.0 ``` Run `carthage update --platform ios,macosx`. @@ -113,7 +113,7 @@ Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`: use_frameworks! target 'YourApp' do - pod 'Socket.IO-Client-Swift', '~> 13.0.0' + pod 'Socket.IO-Client-Swift', '~> 13.1.0' end ``` diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index d002262..62d19be 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "Socket.IO-Client-Swift" s.module_name = "SocketIO" - s.version = "13.0.1" + s.version = "13.1.0" s.summary = "Socket.IO-client for iOS and OS X" s.description = <<-DESC Socket.IO-client for iOS and OS X. @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", - :tag => 'v13.0.1', + :tag => 'v13.1.0', :submodules => true } s.pod_target_xcconfig = { diff --git a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj index 32ecb6c..0157206 100644 --- a/Socket.IO-Client-Swift.xcodeproj/project.pbxproj +++ b/Socket.IO-Client-Swift.xcodeproj/project.pbxproj @@ -532,6 +532,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; ENABLE_BITCODE = YES; + "ENABLE_BITCODE[sdk=macosx*]" = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = ( @@ -604,6 +605,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "Developer ID Application"; ENABLE_BITCODE = YES; + "ENABLE_BITCODE[sdk=macosx*]" = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = ( "$(inherited)", @@ -678,7 +680,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; + ENABLE_BITCODE = YES; + "ENABLE_BITCODE[sdk=macosx*]" = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -747,7 +750,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; + ENABLE_BITCODE = YES; + "ENABLE_BITCODE[sdk=macosx*]" = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; diff --git a/Source/SocketIO/Ack/SocketAckManager.swift b/Source/SocketIO/Ack/SocketAckManager.swift index 67b50aa..cda3271 100644 --- a/Source/SocketIO/Ack/SocketAckManager.swift +++ b/Source/SocketIO/Ack/SocketAckManager.swift @@ -27,6 +27,8 @@ import Foundation /// The status of an ack. public enum SocketAckStatus : String { + // MARK: Cases + /// The ack timed out. case noAck = "NO ACK" } diff --git a/Source/SocketIO/Client/SocketIOClient.swift b/Source/SocketIO/Client/SocketIOClient.swift index acc8ad0..587319c 100644 --- a/Source/SocketIO/Client/SocketIOClient.swift +++ b/Source/SocketIO/Client/SocketIOClient.swift @@ -127,7 +127,14 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { status = .connecting - manager.connectSocket(self) + joinNamespace() + + if manager.status == .connected && nsp == "/" { + // We might not get a connect event for the default nsp, fire immediately + didConnect(toNamespace: nsp) + + return + } guard timeoutAfter != 0 else { return } @@ -183,7 +190,6 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { DefaultSocketLogger.Logger.log("Closing socket", type: logType) leaveNamespace() - didDisconnect(reason: "Disconnect") } /// Send an event to the server, with optional data items. @@ -366,21 +372,15 @@ open class SocketIOClient : NSObject, SocketIOClientSpec { /// Call when you wish to leave a namespace and disconnect this socket. @objc open func leaveNamespace() { - guard nsp != "/" else { return } - - status = .disconnected - manager?.disconnectSocket(self) } /// Joins `nsp`. @objc open func joinNamespace() { - guard nsp != "/" else { return } - DefaultSocketLogger.Logger.log("Joining namespace \(nsp)", type: logType) - manager?.engine?.send("0\(nsp)", withData: []) + manager?.connectSocket(self) } /// Removes handler(s) for a client event. diff --git a/Source/SocketIO/Client/SocketIOClientConfiguration.swift b/Source/SocketIO/Client/SocketIOClientConfiguration.swift index 97f6d91..0ccaace 100644 --- a/Source/SocketIO/Client/SocketIOClientConfiguration.swift +++ b/Source/SocketIO/Client/SocketIOClientConfiguration.swift @@ -129,6 +129,8 @@ public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collectio /// Declares that a type can set configs from a `SocketIOClientConfiguration`. public protocol ConfigSettable { + // MARK: Methods + /// Called when an `ConfigSettable` should set/update its configs from a given configuration. /// /// - parameter config: The `SocketIOClientConfiguration` that should be used to set/update configs. diff --git a/Source/SocketIO/Engine/SocketEngine.swift b/Source/SocketIO/Engine/SocketEngine.swift index ead8671..6b6df7a 100644 --- a/Source/SocketIO/Engine/SocketEngine.swift +++ b/Source/SocketIO/Engine/SocketEngine.swift @@ -44,6 +44,9 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } } + /// A dictionary of extra http headers that will be set during connection. + public var extraHeaders: [String: String]? + /// A queue of engine.io messages waiting for POSTing /// /// **You should not touch this directly** @@ -73,9 +76,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// An array of HTTPCookies that are sent during the connection. public private(set) var cookies: [HTTPCookie]? - /// A dictionary of extra http headers that will be set during connection. - public private(set) var extraHeaders: [String: String]? - /// When `true`, the engine is in the process of switching to WebSockets. /// /// **Do not touch this directly** @@ -112,6 +112,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll public private(set) var urlWebSocket = URL(string: "http://localhost/")! /// If `true`, then the engine is currently in WebSockets mode. + @available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets") public private(set) var websocket = false /// The WebSocket for this engine. @@ -233,7 +234,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll if forceWebsockets { polling = false - websocket = true createWebSocketAndConnect() return } @@ -283,18 +283,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll ws?.delegate = nil // TODO this seems a bit defensive, is this really needed? var req = URLRequest(url: urlWebSocketWithSid) - if cookies != nil { - let headers = HTTPCookie.requestHeaderFields(with: cookies!) - for (headerName, value) in headers { - req.setValue(value, forHTTPHeaderField: headerName) - } - } - - if extraHeaders != nil { - for (headerName, value) in extraHeaders! { - req.setValue(value, forHTTPHeaderField: headerName) - } - } + addHeaders(to: &req) ws = WebSocket(request: req) ws?.callbackQueue = engineQueue @@ -323,19 +312,15 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } private func _disconnect(reason: String) { - guard connected else { return closeOutEngine(reason: reason) } + guard connected && !closed else { return closeOutEngine(reason: reason) } DefaultSocketLogger.Logger.log("Engine is being closed.", type: SocketEngine.logType) - if closed { - return closeOutEngine(reason: reason) - } - - if websocket { + if polling { + disconnectPolling(reason: reason) + } else { sendWebSocketMessage("", withType: .close, withData: []) closeOutEngine(reason: reason) - } else { - disconnectPolling(reason: reason) } } @@ -358,8 +343,9 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll "we'll probably disconnect soon. You should report this.", type: SocketEngine.logType) } + DefaultSocketLogger.Logger.log("Switching to WebSockets", type: SocketEngine.logType) + sendWebSocketMessage("", withType: .upgrade, withData: []) - websocket = true polling = false fastUpgrade = false probing = false @@ -454,6 +440,9 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll // We should upgrade if message == "3probe" { + DefaultSocketLogger.Logger.log("Received probe response, should upgrade to WebSockets", + type: SocketEngine.logType) + upgradeTransport() } @@ -520,7 +509,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll sid = "" waitingForPoll = false waitingForPost = false - websocket = false } private func sendPing() { @@ -603,17 +591,20 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { engineQueue.async { guard self.connected else { return } + guard !self.probing else { + self.probeWait.append((msg, type, data)) - if self.websocket { - DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)", - type: SocketEngine.logType) - self.sendWebSocketMessage(msg, withType: type, withData: data) - } else if !self.probing { + return + } + + if self.polling { DefaultSocketLogger.Logger.log("Writing poll: \(msg) has data: \(data.count != 0)", type: SocketEngine.logType) self.sendPollMessage(msg, withType: type, withData: data) } else { - self.probeWait.append((msg, type, data)) + DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)", + type: SocketEngine.logType) + self.sendWebSocketMessage(msg, withType: type, withData: data) } } } @@ -642,14 +633,14 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll return } - guard websocket else { + guard !polling else { flushProbeWait() return } connected = false - websocket = false + polling = true if let reason = error?.localizedDescription { didError(reason: reason) diff --git a/Source/SocketIO/Engine/SocketEnginePollable.swift b/Source/SocketIO/Engine/SocketEnginePollable.swift index 680f9cf..faec93a 100644 --- a/Source/SocketIO/Engine/SocketEnginePollable.swift +++ b/Source/SocketIO/Engine/SocketEnginePollable.swift @@ -26,7 +26,7 @@ import Foundation /// Protocol that is used to implement socket.io polling support public protocol SocketEnginePollable : SocketEngineSpec { - /// MARK: Properties + // MARK: Properties /// `true` If engine's session has been invalidated. var invalidated: Bool { get } @@ -51,6 +51,8 @@ public protocol SocketEnginePollable : SocketEngineSpec { /// **Do not touch this directly** var waitingForPost: Bool { get set } + // MARK: Methods + /// Call to send a long-polling request. /// /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. @@ -99,7 +101,7 @@ extension SocketEnginePollable { /// /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. public func doPoll() { - guard !websocket && !waitingForPoll && connected && !closed else { return } + guard polling && !waitingForPoll && connected && !closed else { return } var req = URLRequest(url: urlPollingWithSid) addHeaders(to: &req) @@ -149,7 +151,7 @@ extension SocketEnginePollable { private func flushWaitingForPost() { guard postWait.count != 0 && connected else { return } - guard !websocket else { + guard polling else { flushWaitingForPostToWebSocket() return diff --git a/Source/SocketIO/Engine/SocketEngineSpec.swift b/Source/SocketIO/Engine/SocketEngineSpec.swift index 76db405..59ba6b6 100644 --- a/Source/SocketIO/Engine/SocketEngineSpec.swift +++ b/Source/SocketIO/Engine/SocketEngineSpec.swift @@ -28,6 +28,8 @@ import Starscream /// Specifies a SocketEngine. @objc public protocol SocketEngineSpec { + // MARK: Properties + /// The client for this engine. var client: SocketEngineClient? { get set } @@ -50,7 +52,7 @@ import Starscream var engineQueue: DispatchQueue { get } /// A dictionary of extra http headers that will be set during connection. - var extraHeaders: [String: String]? { get } + var extraHeaders: [String: String]? { get set } /// When `true`, the engine is in the process of switching to WebSockets. var fastUpgrade: Bool { get } @@ -80,11 +82,14 @@ import Starscream var urlWebSocket: URL { get } /// If `true`, then the engine is currently in WebSockets mode. + @available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets") var websocket: Bool { get } /// The WebSocket for this engine. var ws: WebSocket? { get } + // MARK: Initializers + /// Creates a new engine. /// /// - parameter client: The client for this engine. @@ -92,6 +97,8 @@ import Starscream /// - parameter options: The options for this engine. init(client: SocketEngineClient, url: URL, options: [String: Any]?) + // MARK: Methods + /// Starts the connection to the server. func connect() @@ -163,10 +170,10 @@ extension SocketEngineSpec { } func createBinaryDataForSend(using data: Data) -> Either { - if websocket { - return .left(Data(bytes: [0x4]) + data) - } else { + if polling { return .right("b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))) + } else { + return .left(Data(bytes: [0x4]) + data) } } diff --git a/Source/SocketIO/Engine/SocketEngineWebsocket.swift b/Source/SocketIO/Engine/SocketEngineWebsocket.swift index 75da709..97a4a72 100644 --- a/Source/SocketIO/Engine/SocketEngineWebsocket.swift +++ b/Source/SocketIO/Engine/SocketEngineWebsocket.swift @@ -28,6 +28,8 @@ import Starscream /// Protocol that is used to implement socket.io WebSocket support public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { + // MARK: Methods + /// Sends an engine.io message through the WebSocket transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. diff --git a/Source/SocketIO/Manager/SocketManager.swift b/Source/SocketIO/Manager/SocketManager.swift index 8a4bca6..5aa53c4 100644 --- a/Source/SocketIO/Manager/SocketManager.swift +++ b/Source/SocketIO/Manager/SocketManager.swift @@ -233,9 +233,6 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa /// /// - parameter socket: The socket to disconnect. open func disconnectSocket(_ socket: SocketIOClient) { - // Make sure we remove socket from nsps - nsps.removeValue(forKey: socket.nsp) - engine?.send("1\(socket.nsp)", withData: []) socket.didDisconnect(reason: "Namespace leave") } @@ -424,6 +421,18 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa engine?.disconnect(reason: "manual reconnect") } + /// Removes the socket from the manager's control. One of the disconnect methods should be called before calling this + /// method. + /// + /// After calling this method the socket should no longer be considered usable. + /// + /// - parameter socket: The socket to remove. + /// - returns: The socket removed, if it was owned by the manager. + @discardableResult + open func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? { + return nsps.removeValue(forKey: socket.nsp) + } + private func tryReconnect(reason: String) { guard reconnecting else { return } diff --git a/Source/SocketIO/Manager/SocketManagerSpec.swift b/Source/SocketIO/Manager/SocketManagerSpec.swift index 8973f43..52bdcab 100644 --- a/Source/SocketIO/Manager/SocketManagerSpec.swift +++ b/Source/SocketIO/Manager/SocketManagerSpec.swift @@ -63,6 +63,9 @@ public protocol SocketManagerSpec : class, SocketEngineClient { /// called on. var handleQueue: DispatchQueue { get set } + /// The sockets in this manager indexed by namespace. + var nsps: [String: SocketIOClient] { get set } + /// If `true`, this manager will try and reconnect on any disconnects. var reconnects: Bool { get set } @@ -114,6 +117,14 @@ public protocol SocketManagerSpec : class, SocketEngineClient { /// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event. func reconnect() + /// Removes the socket from the manager's control. + /// After calling this method the socket should no longer be considered usable. + /// + /// - parameter socket: The socket to remove. + /// - returns: The socket removed, if it was owned by the manager. + @discardableResult + func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? + /// Returns a `SocketIOClient` for the given namespace. This socket shares a transport with the manager. /// /// Calling multiple times returns the same socket. diff --git a/Source/SocketIO/Parse/SocketParsable.swift b/Source/SocketIO/Parse/SocketParsable.swift index 2349ce3..5763e35 100644 --- a/Source/SocketIO/Parse/SocketParsable.swift +++ b/Source/SocketIO/Parse/SocketParsable.swift @@ -24,8 +24,6 @@ import Foundation /// Defines that a type will be able to parse socket.io-protocol messages. public protocol SocketParsable : class { - // MARK: Properties - // MARK: Methods /// Called when the engine has received some binary data that should be attached to a packet. @@ -60,6 +58,8 @@ public enum SocketParsableError : Error { /// Says that a type will be able to buffer binary data before all data for an event has come in. public protocol SocketDataBufferable : class { + // MARK: Properties + /// A list of packets that are waiting for binary data. /// /// The way that socket.io works all data should be sent directly after each packet. @@ -119,7 +119,7 @@ public extension SocketParsable where Self: SocketManagerSpec & SocketDataBuffer } } - var dataArray = String(message.utf16[message.utf16.index(reader.currentIndex, offsetBy: 1).. String { - let substring = message.utf16[currentIndex..