Skip to content

v14.0.0 #1148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 16, 2019
Merged

v14.0.0 #1148

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .swift-version

This file was deleted.

5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v14.0.0

- Add exponential backoff for reconnects, with `reconnectWaitMax` and `randomizationFactor` options [#1149](https://github.com/socketio/socket.io-client-swift/pull/1149)

# v13.4.0

- Add emits with write completion handlers. [#1096](https://github.com/socketio/socket.io-client-swift/issues/1096)
Expand Down Expand Up @@ -69,4 +73,3 @@ Important API changes
- Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
- Makes the framework a single target.
- Updates Starscream to 3.0

4 changes: 3 additions & 1 deletion Socket.IO-Client-Swift.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ Pod::Spec.new do |s|
:tag => 'v13.4.0',
:submodules => true
}

s.swift_version = "4.2"
s.pod_target_xcconfig = {
'SWIFT_VERSION' => '4.0'
}
s.source_files = "Source/SocketIO/**/*.swift", "Source/SocketIO/*.swift"
s.dependency "Starscream", "~> 3.0.2"
s.dependency "Starscream", "~> 3.0.6"
end
4 changes: 4 additions & 0 deletions Socket.IO-Client-Swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
1C6572803D7E252A77A86E5F /* SocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C65763817782DFAC67BE05C /* SocketManager.swift */; };
1C6573B22DC9423CDFC32F05 /* SocketRawView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C657533E849FC3E4342C602 /* SocketRawView.swift */; };
1C657CDE5D510E8E2E573E39 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C6577B639C34EE1C8829D9A /* utils.swift */; };
1C657FBB3F670261780FD72E /* SocketManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C6574AF9687A213814753E4 /* SocketManagerSpec.swift */; };
1C686BE21F869AFD007D8627 /* SocketIOClientConfigurationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C686BD21F869AF1007D8627 /* SocketIOClientConfigurationTest.swift */; };
1C686BE31F869AFD007D8627 /* SocketEngineTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C686BD31F869AF1007D8627 /* SocketEngineTest.swift */; };
Expand Down Expand Up @@ -64,6 +65,7 @@
1C6574AF9687A213814753E4 /* SocketManagerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketManagerSpec.swift; sourceTree = "<group>"; };
1C657533E849FC3E4342C602 /* SocketRawView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketRawView.swift; sourceTree = "<group>"; };
1C65763817782DFAC67BE05C /* SocketManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketManager.swift; sourceTree = "<group>"; };
1C6577B639C34EE1C8829D9A /* utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = "<group>"; };
1C686BD21F869AF1007D8627 /* SocketIOClientConfigurationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketIOClientConfigurationTest.swift; sourceTree = "<group>"; };
1C686BD31F869AF1007D8627 /* SocketEngineTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketEngineTest.swift; sourceTree = "<group>"; };
1C686BD41F869AF1007D8627 /* SocketSideEffectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketSideEffectTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -159,6 +161,7 @@
1C686BD71F869AF1007D8627 /* SocketParserTest.swift */,
1C686BD81F869AF1007D8627 /* SocketNamespacePacketTest.swift */,
DD52BBAC5FAA7730D32CD5BF /* SocketMangerTest.swift */,
1C6577B639C34EE1C8829D9A /* utils.swift */,
);
name = TestSocketIO;
path = Tests/TestSocketIO;
Expand Down Expand Up @@ -501,6 +504,7 @@
1C686BE81F869AFD007D8627 /* SocketNamespacePacketTest.swift in Sources */,
DD52BCCD25EFA76E0F9B313C /* SocketMangerTest.swift in Sources */,
DD52B53F2609D91A683DFCDD /* ManagerObjectiveCTest.m in Sources */,
1C657CDE5D510E8E2E573E39 /* utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
2 changes: 1 addition & 1 deletion Source/SocketIO/Client/SocketIOClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec {
@objc
public private(set) var status = SocketIOStatus.notConnected {
didSet {
handleClientEvent(.statusChange, data: [status])
handleClientEvent(.statusChange, data: [status, status.rawValue])
}
}

Expand Down
16 changes: 15 additions & 1 deletion Source/SocketIO/Client/SocketIOClientOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,14 @@ public enum SocketIOClientOption : ClientOption {
/// The number of times to try and reconnect before giving up. Pass `-1` to [never give up](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
case reconnectAttempts(Int)

/// The number of seconds to wait before reconnect attempts.
/// The minimum number of seconds to wait before reconnect attempts.
case reconnectWait(Int)

/// The maximum number of seconds to wait before reconnect attempts.
case reconnectWaitMax(Int)

/// The randomization factor for calculating reconnect jitter.
case randomizationFactor(Double)

/// Set `true` if your server is using secure transports.
case secure(Bool)
Expand Down Expand Up @@ -125,6 +131,10 @@ public enum SocketIOClientOption : ClientOption {
description = "reconnectAttempts"
case .reconnectWait:
description = "reconnectWait"
case .reconnectWaitMax:
description = "reconnectWaitMax"
case .randomizationFactor:
description = "randomizationFactor"
case .secure:
description = "secure"
case .selfSigned:
Expand Down Expand Up @@ -170,6 +180,10 @@ public enum SocketIOClientOption : ClientOption {
value = attempts
case let .reconnectWait(wait):
value = wait
case let .reconnectWaitMax(wait):
value = wait
case let .randomizationFactor(factor):
value = factor
case let .secure(secure):
value = secure
case let .security(security):
Expand Down
3 changes: 3 additions & 0 deletions Source/SocketIO/Client/SocketIOClientSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ public enum SocketClientEvent : String {

/// Emitted every time there is a change in the client's status.
///
/// The payload for data is [SocketIOClientStatus, Int]. Where the second item is the raw value. Use the second one
/// if you are working in Objective-C.
///
/// Usage:
///
/// ```swift
Expand Down
28 changes: 26 additions & 2 deletions Source/SocketIO/Manager/SocketManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,15 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
/// If `true`, this client will try and reconnect on any disconnects.
public var reconnects = true

/// The number of seconds to wait before attempting to reconnect.
/// The minimum number of seconds to wait before attempting to reconnect.
public var reconnectWait = 10

/// The maximum number of seconds to wait before attempting to reconnect.
public var reconnectWaitMax = 30

/// The randomization factor for calculating reconnect jitter.
public var randomizationFactor = 0.5

/// The status of this manager.
public private(set) var status: SocketIOStatus = .notConnected {
didSet {
Expand Down Expand Up @@ -474,7 +480,21 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
currentReconnectAttempt += 1
connect()

handleQueue.asyncAfter(deadline: DispatchTime.now() + Double(reconnectWait), execute: _tryReconnect)
let interval = reconnectInterval(attempts: currentReconnectAttempt)
DefaultSocketLogger.Logger.log("Scheduling reconnect in \(interval)s", type: SocketManager.logType)
handleQueue.asyncAfter(deadline: DispatchTime.now() + interval, execute: _tryReconnect)
}

func reconnectInterval(attempts: Int) -> Double {
// apply exponential factor
let backoffFactor = pow(1.5, attempts)
let interval = Double(reconnectWait) * Double(truncating: backoffFactor as NSNumber)
// add in a random factor smooth thundering herds
let rand = Double.random(in: 0 ..< 1)
let randomFactor = rand * randomizationFactor * Double(truncating: interval as NSNumber)
// add in random factor, and clamp to min and max values
let combined = interval + randomFactor
return Double(fmax(Double(reconnectWait), fmin(combined, Double(reconnectWaitMax))))
}

/// Sets manager specific configs.
Expand All @@ -493,6 +513,10 @@ open class SocketManager : NSObject, SocketManagerSpec, SocketParsable, SocketDa
self.reconnectAttempts = attempts
case let .reconnectWait(wait):
reconnectWait = abs(wait)
case let .reconnectWaitMax(wait):
reconnectWaitMax = abs(wait)
case let .randomizationFactor(factor):
randomizationFactor = factor
case let .log(log):
DefaultSocketLogger.Logger.log = log
case let .logger(logger):
Expand Down
8 changes: 7 additions & 1 deletion Source/SocketIO/Manager/SocketManagerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ public protocol SocketManagerSpec : AnyObject, SocketEngineClient {
/// If `true`, this manager will try and reconnect on any disconnects.
var reconnects: Bool { get set }

/// The number of seconds to wait before attempting to reconnect.
/// The minimum number of seconds to wait before attempting to reconnect.
var reconnectWait: Int { get set }

/// The maximum number of seconds to wait before attempting to reconnect.
var reconnectWaitMax: Int { get set }

/// The randomization factor for calculating reconnect jitter.
var randomizationFactor: Double { get set }

/// The URL of the socket.io server.
var socketURL: URL { get }
Expand Down
4 changes: 4 additions & 0 deletions Source/SocketIO/Util/SocketExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ extension Dictionary where Key == String, Value == Any {
return .reconnectAttempts(attempts)
case let ("reconnectWait", wait as Int):
return .reconnectWait(wait)
case let ("reconnectWaitMax", wait as Int):
return .reconnectWaitMax(wait)
case let ("randomizationFactor", factor as Double):
return .randomizationFactor(factor)
case let ("secure", secure as Bool):
return .secure(secure)
case let ("security", security as SSLSecurity):
Expand Down
21 changes: 21 additions & 0 deletions Tests/TestSocketIO/SocketMangerTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class SocketMangerTest : XCTestCase {
XCTAssertEqual(manager.handleQueue, DispatchQueue.main)
XCTAssertTrue(manager.reconnects)
XCTAssertEqual(manager.reconnectWait, 10)
XCTAssertEqual(manager.reconnectWaitMax, 30)
XCTAssertEqual(manager.randomizationFactor, 0.5)
XCTAssertEqual(manager.status, .notConnected)
}

Expand All @@ -27,6 +29,21 @@ class SocketMangerTest : XCTestCase {

XCTAssertEqual(manager.config.first!, .secure(true))
}

func testBackoffIntervalCalulation() {
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWaitMax))
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 0), 15)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 1), 22.5)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 2), 33.75)
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 50), Double(manager.reconnectWaitMax))
XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWaitMax))

XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWait))
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 0), Double(manager.reconnectWait))
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 1), 15)
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 2), 22.5)
XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWait))
}

func testManagerCallsConnect() {
setUpSockets()
Expand Down Expand Up @@ -90,13 +107,17 @@ class SocketMangerTest : XCTestCase {
.forceNew(true),
.reconnects(false),
.reconnectWait(5),
.reconnectWaitMax(5),
.randomizationFactor(0.7),
.reconnectAttempts(5)
])

XCTAssertEqual(manager.handleQueue, queue)
XCTAssertTrue(manager.forceNew)
XCTAssertFalse(manager.reconnects)
XCTAssertEqual(manager.reconnectWait, 5)
XCTAssertEqual(manager.reconnectWaitMax, 5)
XCTAssertEqual(manager.randomizationFactor, 0.7)
XCTAssertEqual(manager.reconnectAttempts, 5)
}

Expand Down
13 changes: 13 additions & 0 deletions Tests/TestSocketIO/utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Created by Erik Little on 2019-01-11.
//

import Foundation
@testable import SocketIO

public class OBjcUtils: NSObject {
@objc
public static func setTestStatus(socket: SocketIOClient, status: SocketIOStatus) {
socket.setTestStatus(status)
}
}
2 changes: 2 additions & 0 deletions Tests/TestSocketIOObjc/ManagerObjectiveCTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ - (void)testManagerProperties {
XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue());
XCTAssertTrue(self.manager.reconnects);
XCTAssertEqual(self.manager.reconnectWait, 10);
XCTAssertEqual(self.manager.reconnectWaitMax, 30);
XCTAssertEqual(self.manager.randomizationFactor, 0.5);
XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected);
}

Expand Down
18 changes: 16 additions & 2 deletions Tests/TestSocketIOObjc/SocketObjectiveCTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Merely tests whether the Objective-C api breaks
//

#import "SocketIO_Tests-Swift.h"
#import "SocketObjectiveCTest.h"

@import Dispatch;
Expand Down Expand Up @@ -73,11 +74,11 @@ - (void)testEmitWriteCompletionSyntax {

- (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];
}

Expand All @@ -98,6 +99,19 @@ - (void)testSSLSecurity {
sec = nil;
}

- (void)testStatusChangeHandler {
XCTestExpectation* expect = [self expectationWithDescription:@"statusChange should be correctly called"];

[self.socket on:@"statusChange" callback:^(NSArray* data, SocketAckEmitter* ack) {
XCTAssertTrue([data[1] integerValue] == SocketIOStatusConnecting);
[expect fulfill];
}];

[OBjcUtils setTestStatusWithSocket:self.socket status:SocketIOStatusConnecting];

[self waitForExpectationsWithTimeout:0.3 handler:nil];
}

- (void)setUp {
[super setUp];
NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"];
Expand Down