Skip to content

Commit e5f3fb1

Browse files
authored
Merge pull request #1097 from cobrowseio/writecompletion
Add optional write completion handler for emit's
2 parents 0d8890d + 09fc433 commit e5f3fb1

File tree

11 files changed

+117
-36
lines changed

11 files changed

+117
-36
lines changed

Source/SocketIO/Client/SocketIOClient.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,26 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
213213
/// - parameter items: The items to send with this event. May be left out.
214214
open func emit(_ event: String, _ items: SocketData...) {
215215
do {
216-
try emit(event, with: items.map({ try $0.socketRepresentation() }))
216+
try emit(event, with: items.map({ try $0.socketRepresentation() }), completion: {})
217+
} catch {
218+
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
219+
type: logType)
220+
221+
handleClientEvent(.error, data: [event, items, error])
222+
}
223+
}
224+
225+
/// Send an event to the server, with optional data items and write completion handler.
226+
///
227+
/// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
228+
/// will be emitted. The structure of the error data is `[eventName, items, theError]`
229+
///
230+
/// - parameter event: The event to send.
231+
/// - parameter items: The items to send with this event. May be left out.
232+
/// - parameter completion: Callback called on transport write completion.
233+
open func emit(_ event: String, _ items: SocketData..., completion: @escaping () -> ()) {
234+
do {
235+
try emit(event, with: items.map({ try $0.socketRepresentation() }), completion: completion)
217236
} catch {
218237
DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
219238
type: logType)
@@ -228,7 +247,17 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
228247
/// - parameter items: The items to send with this event. Send an empty array to send no data.
229248
@objc
230249
open func emit(_ event: String, with items: [Any]) {
231-
emit([event] + items)
250+
emit([event] + items, completion: {})
251+
}
252+
253+
/// Same as emit, but meant for Objective-C
254+
///
255+
/// - parameter event: The event to send.
256+
/// - parameter items: The items to send with this event. Send an empty array to send no data.
257+
/// - parameter completion: Callback called on transport write completion.
258+
@objc
259+
open func emit(_ event: String, with items: [Any], completion: @escaping () -> ()) {
260+
emit([event] + items, completion: completion)
232261
}
233262

234263
/// Sends a message to the server, requesting an ack.
@@ -284,8 +313,15 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
284313
return createOnAck([event] + items)
285314
}
286315

287-
func emit(_ data: [Any], ack: Int? = nil, binary: Bool = true, isAck: Bool = false) {
316+
func emit(_ data: [Any], ack: Int? = nil, binary: Bool = true, isAck: Bool = false, completion: (() -> ())? = nil) {
317+
// wrap the completion handler so it always runs async via handlerQueue
318+
let wrappedCompletion = {[weak self] in
319+
guard let this = self else { return }
320+
this.manager?.handleQueue.async { completion?() }
321+
}
322+
288323
guard status == .connected else {
324+
wrappedCompletion();
289325
handleClientEvent(.error, data: ["Tried emitting when not connected"])
290326
return
291327
}
@@ -295,7 +331,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
295331

296332
DefaultSocketLogger.Logger.log("Emitting: \(str), Ack: \(isAck)", type: logType)
297333

298-
manager?.engine?.send(str, withData: packet.binary)
334+
manager?.engine?.send(str, withData: packet.binary, completion: completion != nil ? wrappedCompletion : nil)
299335
}
300336

301337
/// Call when you wish to tell the server that you've received the event for `ack`.

Source/SocketIO/Client/SocketIOClientSpec.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ public protocol SocketIOClientSpec : AnyObject {
101101
/// - parameter items: The items to send with this event. May be left out.
102102
func emit(_ event: String, _ items: SocketData...)
103103

104+
/// Send an event to the server, with optional data items and write completion handler.
105+
///
106+
/// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
107+
/// will be emitted. The structure of the error data is `[eventName, items, theError]`
108+
///
109+
/// - parameter event: The event to send.
110+
/// - parameter items: The items to send with this event. May be left out.
111+
/// - parameter completion: Callback called on transport write completion.
112+
func emit(_ event: String, _ items: SocketData..., completion: @escaping () -> ())
113+
104114
/// Call when you wish to tell the server that you've received the event for `ack`.
105115
///
106116
/// - parameter ack: The ack number.

Source/SocketIO/Engine/SocketEngine.swift

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
4949
/// A queue of engine.io messages waiting for POSTing
5050
///
5151
/// **You should not touch this directly**
52-
public var postWait = [String]()
52+
public var postWait = [Post]()
5353

5454
/// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to
5555
/// disconnect us.
@@ -340,15 +340,15 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
340340
if polling {
341341
disconnectPolling(reason: reason)
342342
} else {
343-
sendWebSocketMessage("", withType: .close, withData: [])
343+
sendWebSocketMessage("", withType: .close, withData: [], completion: {})
344344
closeOutEngine(reason: reason)
345345
}
346346
}
347347

348348
// We need to take special care when we're polling that we send it ASAP
349349
// Also make sure we're on the emitQueue since we're touching postWait
350350
private func disconnectPolling(reason: String) {
351-
postWait.append(String(SocketEnginePacketType.close.rawValue))
351+
postWait.append((String(SocketEnginePacketType.close.rawValue), {}))
352352

353353
doRequest(for: createRequestForPostWithPostWait()) {_, _, _ in }
354354
closeOutEngine(reason: reason)
@@ -366,7 +366,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
366366

367367
DefaultSocketLogger.Logger.log("Switching to WebSockets", type: SocketEngine.logType)
368368

369-
sendWebSocketMessage("", withType: .upgrade, withData: [])
369+
sendWebSocketMessage("", withType: .upgrade, withData: [], completion: {})
370370
polling = false
371371
fastUpgrade = false
372372
probing = false
@@ -384,7 +384,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
384384
DefaultSocketLogger.Logger.log("Flushing probe wait", type: SocketEngine.logType)
385385

386386
for waiter in probeWait {
387-
write(waiter.msg, withType: waiter.type, withData: waiter.data)
387+
write(waiter.msg, withType: waiter.type, withData: waiter.data, completion:waiter.completion)
388388
}
389389

390390
probeWait.removeAll(keepingCapacity: false)
@@ -398,7 +398,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
398398
guard let ws = self.ws else { return }
399399

400400
for msg in postWait {
401-
ws.write(string: msg)
401+
ws.write(string: msg.msg, completion: msg.completion)
402402
}
403403

404404
postWait.removeAll(keepingCapacity: false)
@@ -544,7 +544,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
544544
}
545545

546546
pongsMissed += 1
547-
write("", withType: .ping, withData: [])
547+
write("", withType: .ping, withData: [], completion: {})
548548

549549
engineQueue.asyncAfter(deadline: .now() + .milliseconds(pingInterval)) {[weak self, id = self.sid] in
550550
// Make sure not to ping old connections
@@ -600,7 +600,7 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
600600
DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: SocketEngine.logType)
601601

602602
fastUpgrade = true
603-
sendPollMessage("", withType: .noop, withData: [])
603+
sendPollMessage("", withType: .noop, withData: [], completion: {})
604604
// After this point, we should not send anymore polling messages
605605
}
606606
}
@@ -610,23 +610,27 @@ open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, So
610610
/// - parameter msg: The message to send.
611611
/// - parameter type: The type of this message.
612612
/// - parameter data: Any data that this message has.
613-
open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) {
613+
/// - parameter completion: Callback called on transport write completion.
614+
open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: @escaping () -> ()) {
614615
engineQueue.async {
615-
guard self.connected else { return }
616+
guard self.connected else {
617+
completion()
618+
return
619+
}
616620
guard !self.probing else {
617-
self.probeWait.append((msg, type, data))
621+
self.probeWait.append((msg, type, data, completion))
618622

619623
return
620624
}
621625

622626
if self.polling {
623627
DefaultSocketLogger.Logger.log("Writing poll: \(msg) has data: \(data.count != 0)",
624628
type: SocketEngine.logType)
625-
self.sendPollMessage(msg, withType: type, withData: data)
629+
self.sendPollMessage(msg, withType: type, withData: data, completion: completion)
626630
} else {
627631
DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)",
628632
type: SocketEngine.logType)
629-
self.sendWebSocketMessage(msg, withType: type, withData: data)
633+
self.sendWebSocketMessage(msg, withType: type, withData: data, completion: completion)
630634
}
631635
}
632636
}

Source/SocketIO/Engine/SocketEnginePollable.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public protocol SocketEnginePollable : SocketEngineSpec {
3434
/// A queue of engine.io messages waiting for POSTing
3535
///
3636
/// **You should not touch this directly**
37-
var postWait: [String] { get set }
37+
var postWait: [Post] { get set }
3838

3939
/// The URLSession that will be used for polling.
4040
var session: URLSession? { get }
@@ -65,7 +65,7 @@ public protocol SocketEnginePollable : SocketEngineSpec {
6565
/// - parameter message: The message to send.
6666
/// - parameter withType: The type of message to send.
6767
/// - parameter withData: The data associated with this message.
68-
func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data])
68+
func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: @escaping () -> ())
6969

7070
/// Call to stop polling and invalidate the URLSession.
7171
func stopPolling()
@@ -74,12 +74,15 @@ public protocol SocketEnginePollable : SocketEngineSpec {
7474
// Default polling methods
7575
extension SocketEnginePollable {
7676
func createRequestForPostWithPostWait() -> URLRequest {
77-
defer { postWait.removeAll(keepingCapacity: true) }
77+
defer {
78+
for packet in postWait { packet.completion() }
79+
postWait.removeAll(keepingCapacity: true)
80+
}
7881

7982
var postStr = ""
8083

8184
for packet in postWait {
82-
postStr += "\(packet.utf16.count):\(packet)"
85+
postStr += "\(packet.msg.utf16.count):\(packet.msg)"
8386
}
8487

8588
DefaultSocketLogger.Logger.log("Created POST string: \(postStr)", type: "SocketEnginePolling")
@@ -215,14 +218,17 @@ extension SocketEnginePollable {
215218
/// - parameter message: The message to send.
216219
/// - parameter withType: The type of message to send.
217220
/// - parameter withData: The data associated with this message.
218-
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
221+
/// - parameter completion: Callback called on transport write completion.
222+
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: @escaping () -> ()) {
219223
DefaultSocketLogger.Logger.log("Sending poll: \(message) as type: \(type.rawValue)", type: "SocketEnginePolling")
220224

221-
postWait.append(String(type.rawValue) + message)
225+
postWait.append((String(type.rawValue) + message, completion))
222226

223227
for data in datas {
224228
if case let .right(bin) = createBinaryDataForSend(using: data) {
225-
postWait.append(bin)
229+
// completion handler will be called on initial message write
230+
// TODO: call completion after last message in batch
231+
postWait.append((bin, {}))
226232
}
227233
}
228234

Source/SocketIO/Engine/SocketEngineSpec.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ import Starscream
137137
/// - parameter msg: The message to send.
138138
/// - parameter type: The type of this message.
139139
/// - parameter data: Any data that this message has.
140-
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data])
140+
/// - parameter completion: Callback called on transport write completion.
141+
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: @escaping () -> ())
141142
}
142143

143144
extension SocketEngineSpec {
@@ -162,7 +163,7 @@ extension SocketEngineSpec {
162163
if !cookiesToAdd.isEmpty {
163164
req.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookiesToAdd)
164165
}
165-
166+
166167
if let extraHeaders = extraHeaders {
167168
for (headerName, value) in extraHeaders {
168169
req.setValue(value, forHTTPHeaderField: headerName)
@@ -179,7 +180,7 @@ extension SocketEngineSpec {
179180
}
180181

181182
/// Send an engine message (4)
182-
func send(_ msg: String, withData datas: [Data]) {
183-
write(msg, withType: .message, withData: datas)
183+
func send(_ msg: String, withData datas: [Data], completion: (() -> ())? = nil) {
184+
write(msg, withType: .message, withData: datas, completion: completion ?? {})
184185
}
185186
}

Source/SocketIO/Engine/SocketEngineWebsocket.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@ public protocol SocketEngineWebsocket : SocketEngineSpec {
3737
/// - parameter message: The message to send.
3838
/// - parameter withType: The type of message to send.
3939
/// - parameter withData: The data associated with this message.
40-
func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data])
40+
/// - parameter completion: Callback called on transport write completion.
41+
func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: @escaping () -> ())
4142
}
4243

4344
// WebSocket methods
4445
extension SocketEngineWebsocket {
4546
func probeWebSocket() {
4647
if ws?.isConnected ?? false {
47-
sendWebSocketMessage("probe", withType: .ping, withData: [])
48+
sendWebSocketMessage("probe", withType: .ping, withData: [], completion: {})
4849
}
4950
}
5051

@@ -55,14 +56,15 @@ extension SocketEngineWebsocket {
5556
/// - parameter message: The message to send.
5657
/// - parameter withType: The type of message to send.
5758
/// - parameter withData: The data associated with this message.
58-
public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
59+
/// - parameter completion: Callback called on transport write completion.
60+
public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: @escaping () -> ()) {
5961
DefaultSocketLogger.Logger.log("Sending ws: \(str) as type: \(type.rawValue)", type: "SocketEngineWebSocket")
6062

6163
ws?.write(string: "\(type.rawValue)\(str)")
6264

6365
for data in datas {
6466
if case let .left(bin) = createBinaryDataForSend(using: data) {
65-
ws?.write(data: bin)
67+
ws?.write(data: bin, completion: completion)
6668
}
6769
}
6870
}

Source/SocketIO/Manager/SocketManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
286286
/// - parameter items: The data to send with this event.
287287
open func emitAll(_ event: String, withItems items: [Any]) {
288288
forAll {socket in
289-
socket.emit(event, with: items)
289+
socket.emit(event, with: items, completion: {})
290290
}
291291
}
292292

Source/SocketIO/Util/SocketTypes.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ public typealias AckCallback = ([Any]) -> ()
7373
/// A typealias for a normal callback.
7474
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
7575

76+
/// A typealias for a queued POST
77+
public typealias Post = (msg: String, completion: (() -> ()))
78+
7679
typealias JSON = [String: Any]
77-
typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data])
80+
typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data], completion: (() -> ()))
7881
typealias ProbeWaitQueue = [Probe]
7982

8083
enum Either<E, V> {

Tests/TestSocketIO/SocketMangerTest.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public class TestSocket : SocketIOClient {
198198
super.didDisconnect(reason: reason)
199199
}
200200

201-
public override func emit(_ event: String, with items: [Any]) {
201+
public override func emit(_ event: String, with items: [Any], completion: @escaping () -> ()) {
202202
expectations[ManagerExpectation.emitAllEventCalled]?.fulfill()
203203
expectations[ManagerExpectation.emitAllEventCalled] = nil
204204

Tests/TestSocketIO/SocketSideEffectTest.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ class SocketSideEffectTest: XCTestCase {
2727
XCTAssertEqual(socket.currentAck, 1)
2828
}
2929

30+
func testEmitCompletionSyntax() {
31+
socket.emit("test", completion: {})
32+
socket.emit("test", "thing", completion: {})
33+
}
34+
3035
func testHandleAck() {
3136
let expect = expectation(description: "handled ack")
3237
socket.emitWithAck("test").timingOut(after: 0) {data in
@@ -506,5 +511,5 @@ class TestEngine : SocketEngineSpec {
506511
func flushWaitingForPostToWebSocket() { }
507512
func parseEngineData(_ data: Data) { }
508513
func parseEngineMessage(_ message: String) { }
509-
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) { }
514+
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: @escaping () -> ()) { }
510515
}

0 commit comments

Comments
 (0)