Merge branch 'development'

* development:
  update changelog
  Remove MARK: for websockets methods
  Use callbacks instead of delegate methods for Starscream
  Fix exclusive access issue
  Execute acks sync. Implement #950
  Fixes build warnings caused by documentation issues.
  update starscream carthage
This commit is contained in:
Erik Little 2018-03-11 10:57:14 -04:00
commit 31aed9e793
No known key found for this signature in database
GPG Key ID: B8E1F067FE8DCAAF
14 changed files with 81 additions and 69 deletions

View File

@ -1 +1 @@
4.0.2 system

View File

@ -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 [#923](https://github.com/socketio/socket.io-client-swift/issues/923)
- Fix [#894](https://github.com/socketio/socket.io-client-swift/issues/894) - Fix [#894](https://github.com/socketio/socket.io-client-swift/issues/894)

View File

@ -1,3 +1,3 @@
github "daltoniam/Starscream" "3.0.3"
github "daltoniam/common-crypto-spm" "1.1.0" github "daltoniam/common-crypto-spm" "1.1.0"
github "daltoniam/zlib-spm" "1.1.0" github "daltoniam/zlib-spm" "1.1.0"
github "daltoniam/Starscream" "3.0.2"

View File

@ -108,7 +108,7 @@ public final class OnAckCallback : NSObject {
/// Completes an emitWithAck. If this isn't called, the emit never happens. /// 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. /// - parameter callback: The callback called when an ack is received, or when a timeout happens.
/// To check for timeout, use `SocketAckStatus`'s `noAck` case. /// To check for timeout, use `SocketAckStatus`'s `noAck` case.
@objc @objc
@ -121,9 +121,9 @@ public final class OnAckCallback : NSObject {
guard seconds != 0 else { return } guard seconds != 0 else { return }
socket.manager?.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {[weak socket] in 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)
} }
} }

View File

@ -58,31 +58,20 @@ private struct SocketAck : Hashable {
} }
} }
struct SocketAckManager { class SocketAckManager {
private var acks = Set<SocketAck>(minimumCapacity: 1) private var acks = Set<SocketAck>(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)) acks.insert(SocketAck(ack: ack, callback: callback))
} }
/// Should be called on handle queue /// Should be called on handle queue
mutating func executeAck(_ ack: Int, with items: [Any], onQueue: DispatchQueue) { func executeAck(_ ack: Int, with items: [Any]) {
ackSemaphore.wait() acks.remove(SocketAck(ack: ack))?.callback(items)
defer { ackSemaphore.signal() }
let ack = acks.remove(SocketAck(ack: ack))
onQueue.async() { ack?.callback(items) }
} }
/// Should be called on handle queue /// Should be called on handle queue
mutating func timeoutAck(_ ack: Int, onQueue: DispatchQueue) { func timeoutAck(_ ack: Int) {
ackSemaphore.wait() acks.remove(SocketAck(ack: ack))?.callback?([SocketAckStatus.noAck.rawValue])
defer { ackSemaphore.signal() }
let ack = acks.remove(SocketAck(ack: ack))
onQueue.async() {
ack?.callback?([SocketAckStatus.noAck.rawValue])
}
} }
} }

View File

@ -75,7 +75,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
} }
} }
var ackHandlers = SocketAckManager() let ackHandlers = SocketAckManager()
private(set) var currentAck = -1 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. /// Type safe way to create a new SocketIOClient. `opts` can be omitted.
/// ///
/// - parameter manager: The manager for this socket. /// - parameter manager: The manager for this socket.
/// - parameter socketURL: The url of the socket.io server. /// - parameter nsp: The namespace of the socket.
@objc @objc
public init(manager: SocketManagerSpec, nsp: String) { public init(manager: SocketManagerSpec, nsp: String) {
self.manager = manager 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 /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection
/// has failed. Pass 0 to never timeout. /// 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 @objc
open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) { open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) {
assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")
@ -213,7 +213,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
/// Same as emit, but meant for Objective-C /// Same as emit, but meant for Objective-C
/// ///
/// - parameter event: The event to send. /// - 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 @objc
open func emit(_ event: String, with items: [Any]) { open func emit(_ event: String, with items: [Any]) {
guard status == .connected else { guard status == .connected else {
@ -270,7 +270,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
/// ``` /// ```
/// ///
/// - parameter event: The event to send. /// - 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. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent.
@objc @objc
open func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { 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. /// - parameter data: The data sent back with this ack.
@objc @objc
open func handleAck(_ ack: Int, data: [Any]) { 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) 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. /// Called on socket.io specific events.
@ -334,7 +334,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
/// - parameter event: The name of the event. /// - parameter event: The name of the event.
/// - parameter data: The data that was sent with this 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 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 @objc
open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) { open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) {
guard status == .connected || isInternalMessage else { return } guard status == .connected || isInternalMessage else { return }

View File

@ -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 /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection
/// has failed. Pass 0 to never timeout. /// 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: (() -> ())?) func connect(timeoutAfter: Double, withHandler handler: (() -> ())?)
/// Called when the client connects to a namespace. If the client was created with a namespace upfront, /// 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 event: The name of the event.
/// - parameter data: The data that was sent with this 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 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) 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 /// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the

View File

@ -280,7 +280,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
} }
private func createWebSocketAndConnect() { private func createWebSocketAndConnect() {
ws?.delegate = nil // TODO this seems a bit defensive, is this really needed?
var req = URLRequest(url: urlWebSocketWithSid) var req = URLRequest(url: urlWebSocketWithSid)
addHeaders(to: &req) addHeaders(to: &req)
@ -288,10 +287,33 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
ws = WebSocket(request: req) ws = WebSocket(request: req)
ws?.callbackQueue = engineQueue ws?.callbackQueue = engineQueue
ws?.enableCompression = compress ws?.enableCompression = compress
ws?.delegate = self
ws?.disableSSLCertValidation = selfSigned ws?.disableSSLCertValidation = selfSigned
ws?.security = security?.security 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() ws?.connect()
} }
@ -461,8 +483,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
/// Parses a raw engine.io packet. /// Parses a raw engine.io packet.
/// ///
/// - parameter message: The message to parse. /// - 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) { public func parseEngineMessage(_ message: String) {
DefaultSocketLogger.Logger.log("Got message: \(message)", type: SocketEngine.logType) 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. /// Writes a message to engine.io, independent of transport.
/// ///
/// - parameter msg: The message to send. /// - parameter msg: The message to send.
/// - parameter withType: The type of this message. /// - parameter type: The type of this message.
/// - parameter withData: Any data that this message has. /// - parameter data: Any data that this message has.
public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { public func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) {
engineQueue.async { engineQueue.async {
guard self.connected else { return } 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. private func websocketDidConnect() {
public func websocketDidConnect(socket: WebSocketClient) {
if !forceWebsockets { if !forceWebsockets {
probing = true probing = true
probeWebSocket() probeWebSocket()
@ -623,8 +642,7 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
} }
} }
/// Delegate method for disconnection. private func websocketDidDisconnect(error: Error?) {
public func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
probing = false probing = false
if closed { if closed {

View File

@ -130,15 +130,13 @@ import Starscream
/// Parses a raw engine.io packet. /// Parses a raw engine.io packet.
/// ///
/// - parameter message: The message to parse. /// - 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) func parseEngineMessage(_ message: String)
/// Writes a message to engine.io, independent of transport. /// Writes a message to engine.io, independent of transport.
/// ///
/// - parameter msg: The message to send. /// - parameter msg: The message to send.
/// - parameter withType: The type of this message. /// - parameter type: The type of this message.
/// - parameter withData: Any data that this message has. /// - parameter data: Any data that this message has.
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data])
} }

View File

@ -27,7 +27,7 @@ import Foundation
import Starscream import Starscream
/// Protocol that is used to implement socket.io WebSocket support /// Protocol that is used to implement socket.io WebSocket support
public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { public protocol SocketEngineWebsocket : SocketEngineSpec {
// MARK: Methods // MARK: Methods
/// Sends an engine.io message through the WebSocket transport. /// 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)
}
} }

View File

@ -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 /// This will remove the socket for the manager's control, and make the socket instance useless and ready for
/// releasing. /// releasing.
/// ///
/// - parameter forNamespace: The namespace to disconnect from. /// - parameter nsp: The namespace to disconnect from.
open func disconnectSocket(forNamespace nsp: String) { open func disconnectSocket(forNamespace nsp: String) {
guard let socket = nsps.removeValue(forKey: nsp) else { guard let socket = nsps.removeValue(forKey: nsp) else {
DefaultSocketLogger.Logger.log("Could not find socket for \(nsp) to disconnect", 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. /// Same as `emitAll(_:_:)`, but meant for Objective-C.
/// ///
/// - parameter event: The event to send. /// - 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]) { open func emitAll(_ event: String, withItems items: [Any]) {
forAll {socket in forAll {socket in
socket.emit(event, with: items) 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. /// Call one of the `disconnectSocket` methods on this class to remove the socket from manager control.
/// Or call `SocketIOClient.disconnect()` on the client. /// 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. /// - returns: A `SocketIOClient` for the given namespace.
open func socket(forNamespace nsp: String) -> SocketIOClient { open func socket(forNamespace nsp: String) -> SocketIOClient {
assert(nsp.hasPrefix("/"), "forNamespace must have a leading /") assert(nsp.hasPrefix("/"), "forNamespace must have a leading /")

View File

@ -103,13 +103,13 @@ public protocol SocketManagerSpec : class, SocketEngineClient {
/// Disconnects the socket associated with `forNamespace`. /// 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) func disconnectSocket(forNamespace nsp: String)
/// Sends an event to the server on all namespaces in this manager. /// Sends an event to the server on all namespaces in this manager.
/// ///
/// - parameter event: The event to send. /// - 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]) func emitAll(_ event: String, withItems items: [Any])
/// Tries to reconnect to the server. /// 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. /// Call one of the `disconnectSocket` methods on the implementing class to remove the socket from manager control.
/// Or call `SocketIOClient.disconnect()` on the client. /// 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. /// - returns: A `SocketIOClient` for the given namespace.
func socket(forNamespace nsp: String) -> SocketIOClient func socket(forNamespace nsp: String) -> SocketIOClient
} }

View File

@ -21,7 +21,7 @@ class SocketAckManagerTest : XCTestCase {
} }
ackManager.addAck(1, callback: callback) ackManager.addAck(1, callback: callback)
ackManager.executeAck(1, with: itemsArray, onQueue: DispatchQueue.main) ackManager.executeAck(1, with: itemsArray)
waitForExpectations(timeout: 3.0, handler: nil) waitForExpectations(timeout: 3.0, handler: nil)
} }
@ -44,7 +44,7 @@ class SocketAckManagerTest : XCTestCase {
} }
ackManager.addAck(1, callback: callback) ackManager.addAck(1, callback: callback)
ackManager.timeoutAck(1, onQueue: DispatchQueue.main) ackManager.timeoutAck(1)
waitForExpectations(timeout: 0.2, handler: nil) waitForExpectations(timeout: 0.2, handler: nil)
} }

View File

@ -38,6 +38,19 @@ class SocketSideEffectTest: XCTestCase {
waitForExpectations(timeout: 3, handler: nil) 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() { func testHandleAck2() {
let expect = expectation(description: "handled ack2") let expect = expectation(description: "handled ack2")
socket.emitWithAck("test").timingOut(after: 0) {data in socket.emitWithAck("test").timingOut(after: 0) {data in