Merge branch 'development'

* development:
  Remove unnecessary function calls.
  Fix build
  Don’t create empty closures when we don’t need them. Fixes https://github.com/socketio/socket.io-client-swift/issues/1118.
  Propagate http header callback from websocket back to client.
  clean up emit completion code
  update cartfile too
  update deps: Fix #1105
  update xcode settings
  convert SocketData to serialisable socket representation
  Add a variadic method for emit completion handlers in swift
  no need to capture completion hander in wrapper
  wrap completion handler to run on handleQueue
  add optional write completion handler for emit's
This commit is contained in:
Erik Little 2018-11-28 17:08:17 -05:00
commit e4f63e8f15
No known key found for this signature in database
GPG Key ID: 62F837E56F4E9320
16 changed files with 155 additions and 68 deletions

View File

@ -1 +1 @@
github "daltoniam/Starscream" "3.0.5"
github "daltoniam/Starscream" "3.0.6"

View File

@ -1,31 +1,13 @@
{
"object": {
"pins": [
{
"package": "SSCommonCrypto",
"repositoryURL": "https://github.com/daltoniam/common-crypto-spm",
"state": {
"branch": null,
"revision": "2eb3aff0fb57f92f5722fac5d6d20bf64669ca66",
"version": "1.1.0"
}
},
{
"package": "Starscream",
"repositoryURL": "https://github.com/daltoniam/Starscream",
"state": {
"branch": null,
"revision": "114e5df9b6251970a069e8f1c0cbb5802759f0a9",
"version": "3.0.5"
}
},
{
"package": "SSCZLib",
"repositoryURL": "https://github.com/daltoniam/zlib-spm.git",
"state": {
"branch": null,
"revision": "83ac8d719a2f3aa775dbdf116a57f56fb2c49abb",
"version": "1.1.0"
"revision": "ebdc260ea64e68f7569c62e8744b5cd15d3a49d6",
"version": "3.0.6"
}
}
]

View File

@ -391,7 +391,7 @@
attributes = {
LastSwiftMigration = 0730;
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0900;
LastUpgradeCheck = 1000;
TargetAttributes = {
572EF2371B51F18A00EEBB58 = {
CreatedOnToolsVersion = 6.4;
@ -523,18 +523,20 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_IDENTITY = "Mac Developer";
ENABLE_BITCODE = YES;
"ENABLE_BITCODE[sdk=macosx*]" = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -596,18 +598,20 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_IDENTITY = "Mac Developer";
ENABLE_BITCODE = YES;
"ENABLE_BITCODE[sdk=macosx*]" = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -40,7 +40,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
@ -70,7 +69,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -204,16 +204,17 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
leaveNamespace()
}
/// Send an event to the server, with optional data items.
/// Send an event to the server, with optional data items and optional write completion handler.
///
/// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
/// will be emitted. The structure of the error data is `[eventName, items, theError]`
///
/// - parameter event: The event to send.
/// - parameter items: The items to send with this event. May be left out.
open func emit(_ event: String, _ items: SocketData...) {
/// - parameter completion: Callback called on transport write completion.
open func emit(_ event: String, _ items: SocketData..., completion: (() -> ())? = nil) {
do {
try emit(event, with: items.map({ try $0.socketRepresentation() }))
try emit(event, with: items.map({ try $0.socketRepresentation() }), completion: completion)
} catch {
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
type: logType)
@ -231,6 +232,16 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
emit([event] + items)
}
/// Same as emit, but meant for Objective-C
///
/// - parameter event: The event to send.
/// - parameter items: The items to send with this event. Send an empty array to send no data.
/// - parameter completion: Callback called on transport write completion.
@objc
open func emit(_ event: String, with items: [Any], completion: (() -> ())? = nil) {
emit([event] + items, completion: completion)
}
/// Sends a message to the server, requesting an ack.
///
/// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack.
@ -284,8 +295,22 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
return createOnAck([event] + items)
}
func emit(_ data: [Any], ack: Int? = nil, binary: Bool = true, isAck: Bool = false) {
func emit(_ data: [Any],
ack: Int? = nil,
binary: Bool = true,
isAck: Bool = false,
completion: (() -> ())? = nil
) {
// wrap the completion handler so it always runs async via handlerQueue
let wrappedCompletion: (() -> ())? = (completion == nil) ? nil : {[weak self] in
guard let this = self else { return }
this.manager?.handleQueue.async {
completion!()
}
}
guard status == .connected else {
wrappedCompletion?()
handleClientEvent(.error, data: ["Tried emitting when not connected"])
return
}
@ -295,7 +320,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
DefaultSocketLogger.Logger.log("Emitting: \(str), Ack: \(isAck)", type: logType)
manager?.engine?.send(str, withData: packet.binary)
manager?.engine?.send(str, withData: packet.binary, completion: wrappedCompletion)
}
/// Call when you wish to tell the server that you've received the event for `ack`.

View File

@ -92,14 +92,15 @@ public protocol SocketIOClientSpec : AnyObject {
/// Disconnects the socket.
func disconnect()
/// Send an event to the server, with optional data items.
/// Send an event to the server, with optional data items and optional write completion handler.
///
/// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
/// will be emitted. The structure of the error data is `[eventName, items, theError]`
///
/// - parameter event: The event to send.
/// - parameter items: The items to send with this event. May be left out.
func emit(_ event: String, _ items: SocketData...)
/// - parameter completion: Callback called on transport write completion.
func emit(_ event: String, _ items: SocketData..., completion: (() -> ())?)
/// Call when you wish to tell the server that you've received the event for `ack`.
///
@ -334,4 +335,16 @@ public enum SocketClientEvent : String {
/// }
/// ```
case statusChange
/// Emitted when when upgrading the http connection to a websocket connection.
///
/// Usage:
///
/// ```swift
/// socket.on(clientEvent: .websocketUpgrade) {data, ack in
/// let headers = (data as [Any])[0]
/// // Some header logic
/// }
/// ```
case websocketUpgrade
}

View File

@ -49,7 +49,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
/// A queue of engine.io messages waiting for POSTing
///
/// **You should not touch this directly**
public var postWait = [String]()
public var postWait = [Post]()
/// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to
/// disconnect us.
@ -313,6 +313,12 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
this.parseEngineMessage(message)
}
ws?.onHttpResponseHeaders = {[weak self] headers in
guard let this = self else { return }
this.client?.engineDidWebsocketUpgrade(headers: headers)
}
ws?.connect()
}
@ -340,7 +346,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
if polling {
disconnectPolling(reason: reason)
} else {
sendWebSocketMessage("", withType: .close, withData: [])
sendWebSocketMessage("", withType: .close, withData: [], completion: nil)
closeOutEngine(reason: reason)
}
}
@ -348,7 +354,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
// We need to take special care when we're polling that we send it ASAP
// Also make sure we're on the emitQueue since we're touching postWait
private func disconnectPolling(reason: String) {
postWait.append(String(SocketEnginePacketType.close.rawValue))
postWait.append((String(SocketEnginePacketType.close.rawValue), {}))
doRequest(for: createRequestForPostWithPostWait()) {_, _, _ in }
closeOutEngine(reason: reason)
@ -366,7 +372,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
DefaultSocketLogger.Logger.log("Switching to WebSockets", type: SocketEngine.logType)
sendWebSocketMessage("", withType: .upgrade, withData: [])
sendWebSocketMessage("", withType: .upgrade, withData: [], completion: nil)
polling = false
fastUpgrade = false
probing = false
@ -384,7 +390,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
DefaultSocketLogger.Logger.log("Flushing probe wait", type: SocketEngine.logType)
for waiter in probeWait {
write(waiter.msg, withType: waiter.type, withData: waiter.data)
write(waiter.msg, withType: waiter.type, withData: waiter.data, completion: waiter.completion)
}
probeWait.removeAll(keepingCapacity: false)
@ -398,7 +404,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
guard let ws = self.ws else { return }
for msg in postWait {
ws.write(string: msg)
ws.write(string: msg.msg, completion: msg.completion)
}
postWait.removeAll(keepingCapacity: false)
@ -544,7 +550,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
}
pongsMissed += 1
write("", withType: .ping, withData: [])
write("", withType: .ping, withData: [], completion: nil)
engineQueue.asyncAfter(deadline: .now() + .milliseconds(pingInterval)) {[weak self, id = self.sid] in
// Make sure not to ping old connections
@ -600,7 +606,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: SocketEngine.logType)
fastUpgrade = true
sendPollMessage("", withType: .noop, withData: [])
sendPollMessage("", withType: .noop, withData: [], completion: nil)
// After this point, we should not send anymore polling messages
}
}
@ -610,11 +616,15 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
/// - parameter msg: The message to send.
/// - parameter type: The type of this message.
/// - parameter data: Any data that this message has.
open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) {
/// - parameter completion: Callback called on transport write completion.
open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: (() -> ())? = nil) {
engineQueue.async {
guard self.connected else { return }
guard self.connected else {
completion?()
return
}
guard !self.probing else {
self.probeWait.append((msg, type, data))
self.probeWait.append((msg, type, data, completion))
return
}
@ -622,11 +632,11 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
if self.polling {
DefaultSocketLogger.Logger.log("Writing poll: \(msg) has data: \(data.count != 0)",
type: SocketEngine.logType)
self.sendPollMessage(msg, withType: type, withData: data)
self.sendPollMessage(msg, withType: type, withData: data, completion: completion)
} else {
DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)",
type: SocketEngine.logType)
self.sendWebSocketMessage(msg, withType: type, withData: data)
self.sendWebSocketMessage(msg, withType: type, withData: data, completion: completion)
}
}
}
@ -662,7 +672,9 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
connected = false
polling = true
if let reason = error?.localizedDescription {
if let error = error as? WSError {
didError(reason: "\(error.message). code=\(error.code), type=\(error.type)")
} else if let reason = error?.localizedDescription {
didError(reason: reason)
} else {
client?.engineDidClose(reason: "Socket Disconnected")

View File

@ -59,4 +59,9 @@ import Foundation
///
/// - parameter data: The data the engine received.
func parseEngineBinaryData(_ data: Data)
/// Called when when upgrading the http connection to a websocket connection.
///
/// - parameter headers: The http headers.
func engineDidWebsocketUpgrade(headers: [String: String])
}

View File

@ -34,7 +34,7 @@ public protocol SocketEnginePollable : SocketEngineSpec {
/// A queue of engine.io messages waiting for POSTing
///
/// **You should not touch this directly**
var postWait: [String] { get set }
var postWait: [Post] { get set }
/// The URLSession that will be used for polling.
var session: URLSession? { get }
@ -65,7 +65,7 @@ public protocol SocketEnginePollable : SocketEngineSpec {
/// - parameter message: The message to send.
/// - parameter withType: The type of message to send.
/// - parameter withData: The data associated with this message.
func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data])
func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: (() -> ())?)
/// Call to stop polling and invalidate the URLSession.
func stopPolling()
@ -74,12 +74,15 @@ public protocol SocketEnginePollable : SocketEngineSpec {
// Default polling methods
extension SocketEnginePollable {
func createRequestForPostWithPostWait() -> URLRequest {
defer { postWait.removeAll(keepingCapacity: true) }
defer {
for packet in postWait { packet.completion?() }
postWait.removeAll(keepingCapacity: true)
}
var postStr = ""
for packet in postWait {
postStr += "\(packet.utf16.count):\(packet)"
postStr += "\(packet.msg.utf16.count):\(packet.msg)"
}
DefaultSocketLogger.Logger.log("Created POST string: \(postStr)", type: "SocketEnginePolling")
@ -215,14 +218,15 @@ extension SocketEnginePollable {
/// - parameter message: The message to send.
/// - parameter withType: The type of message to send.
/// - parameter withData: The data associated with this message.
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
/// - parameter completion: Callback called on transport write completion.
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: (() -> ())? = nil) {
DefaultSocketLogger.Logger.log("Sending poll: \(message) as type: \(type.rawValue)", type: "SocketEnginePolling")
postWait.append(String(type.rawValue) + message)
postWait.append((String(type.rawValue) + message, completion))
for data in datas {
if case let .right(bin) = createBinaryDataForSend(using: data) {
postWait.append(bin)
postWait.append((bin, {}))
}
}

View File

@ -137,7 +137,8 @@ import Starscream
/// - parameter msg: The message to send.
/// - 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])
/// - parameter completion: Callback called on transport write completion.
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: (() -> ())?)
}
extension SocketEngineSpec {
@ -179,7 +180,7 @@ extension SocketEngineSpec {
}
/// Send an engine message (4)
func send(_ msg: String, withData datas: [Data]) {
write(msg, withType: .message, withData: datas)
func send(_ msg: String, withData datas: [Data], completion: (() -> ())? = nil) {
write(msg, withType: .message, withData: datas, completion: completion)
}
}

View File

@ -37,14 +37,18 @@ public protocol SocketEngineWebsocket : SocketEngineSpec {
/// - parameter message: The message to send.
/// - parameter withType: The type of message to send.
/// - parameter withData: The data associated with this message.
func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data])
/// - parameter completion: Callback called on transport write completion.
func sendWebSocketMessage(_ str: String,
withType type: SocketEnginePacketType,
withData datas: [Data],
completion: (() -> ())?)
}
// WebSocket methods
extension SocketEngineWebsocket {
func probeWebSocket() {
if ws?.isConnected ?? false {
sendWebSocketMessage("probe", withType: .ping, withData: [])
sendWebSocketMessage("probe", withType: .ping, withData: [], completion: nil)
}
}
@ -55,14 +59,19 @@ extension SocketEngineWebsocket {
/// - parameter message: The message to send.
/// - parameter withType: The type of message to send.
/// - parameter withData: The data associated with this message.
public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
/// - parameter completion: Callback called on transport write completion.
public func sendWebSocketMessage(_ str: String,
withType type: SocketEnginePacketType,
withData datas: [Data],
completion: (() -> ())?
) {
DefaultSocketLogger.Logger.log("Sending ws: \(str) as type: \(type.rawValue)", type: "SocketEngineWebSocket")
ws?.write(string: "\(type.rawValue)\(str)")
for data in datas {
if case let .left(bin) = createBinaryDataForSend(using: data) {
ws?.write(data: bin)
ws?.write(data: bin, completion: completion)
}
}
}

View File

@ -286,7 +286,7 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
/// - 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)
socket.emit(event, with: items, completion: nil)
}
}
@ -377,6 +377,18 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
}
}
/// Called when when upgrading the http connection to a websocket connection.
///
/// - parameter headers: The http headers.
open func engineDidWebsocketUpgrade(headers: [String: String]) {
handleQueue.async {
self._engineDidWebsocketUpgrade(headers: headers)
}
}
private func _engineDidWebsocketUpgrade(headers: [String: String]) {
emitAll(clientEvent: .websocketUpgrade, data: [headers])
}
/// Called when the engine has a message that must be parsed.
///
/// - parameter msg: The message that needs parsing.

View File

@ -73,8 +73,11 @@ public typealias AckCallback = ([Any]) -> ()
/// A typealias for a normal callback.
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
/// A typealias for a queued POST
public typealias Post = (msg: String, completion: (() -> ())?)
typealias JSON = [String: Any]
typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data])
typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data], completion: (() -> ())?)
typealias ProbeWaitQueue = [Probe]
enum Either<E, V> {

View File

@ -198,7 +198,7 @@ public class TestSocket : SocketIOClient {
super.didDisconnect(reason: reason)
}
public override func emit(_ event: String, with items: [Any]) {
public override func emit(_ event: String, with items: [Any], completion: (() -> ())?) {
expectations[ManagerExpectation.emitAllEventCalled]?.fulfill()
expectations[ManagerExpectation.emitAllEventCalled] = nil

View File

@ -27,6 +27,11 @@ class SocketSideEffectTest: XCTestCase {
XCTAssertEqual(socket.currentAck, 1)
}
func testEmitCompletionSyntax() {
socket.emit("test", completion: {})
socket.emit("test", "thing", completion: {})
}
func testHandleAck() {
let expect = expectation(description: "handled ack")
socket.emitWithAck("test").timingOut(after: 0) {data in
@ -506,5 +511,5 @@ class TestEngine : SocketEngineSpec {
func flushWaitingForPostToWebSocket() { }
func parseEngineData(_ data: Data) { }
func parseEngineMessage(_ message: String) { }
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { }
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: (() -> ())?) { }
}

View File

@ -67,6 +67,20 @@
[self.socket emit:@"testEmit" with:@[@YES]];
}
- (void)testEmitWriteCompletionSyntax {
[self.socket emit:@"testEmit" with:@[@YES] completion:^{}];
}
- (void)testEmitWriteCompletion {
XCTestExpectation* expect = [self expectationWithDescription:@"Write completion should be called"];
[self.socket emit:@"testEmit" with:@[@YES] completion:^{
[expect fulfill];
}];
[self waitForExpectationsWithTimeout:0.3 handler:nil];
}
- (void)testRawEmitSyntax {
[[self.socket rawEmitView] emit:@"myEvent" with:@[@1]];
}