diff --git a/CHANGELOG.md b/CHANGELOG.md index 4462ed2c1..45878567d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log -## Unreleased +## [1.2.20](https://github.com/ably/ably-cocoa/tree/1.2.20) + +[Full Changelog](https://github.com/ably/ably-cocoa/compare/1.2.19...1.2.20) **Public API changes:** @@ -10,6 +12,23 @@ - The ably-cocoa library no longer calls any of the `ARTLog` methods in the `ARTLog (Shorthand)` category, nor the `-logWithError:` method. Be aware that if you have created a custom subclass of `ARTLog` which overrides any of these methods, they will no longer be called. `ARTLog` now performs all of its logging using only the `-log:withLevel:` method. +**Fixed bugs:** + +- Fallback host is being discarded before `fallbackRetryTimeout` elapses [\#1683](https://github.com/ably/ably-cocoa/issues/1683) +- Compilation error in SocketRocket in Xcode 14.3 [\#1591](https://github.com/ably/ably-cocoa/issues/1591) +- Call to `_connect` in `ARTRealtimeInternal` constructor [\#1566](https://github.com/ably/ably-cocoa/issues/1566) +- SCNetworkReachabilitySetCallback \(Crashed: io.ably.main\) [\#1380](https://github.com/ably/ably-cocoa/issues/1380) +- Crash in ARTOSReachability [\#593](https://github.com/ably/ably-cocoa/issues/593) + +**Closed issues:** + +- Run tests in Ably-iOS and Ably-macOS using Xcode 14.3 [\#1653](https://github.com/ably/ably-cocoa/issues/1653) +- Emit file name and line number with every logged message [\#1643](https://github.com/ably/ably-cocoa/issues/1643) +- Thread running at QOS\_CLASS\_USER\_INTERACTIVE waiting on a lower QoS thread running at QOS\_CLASS\_DEFAULT. Investigate ways to avoid priority inversions [\#1569](https://github.com/ably/ably-cocoa/issues/1569) +- App Store publication issues when using Carthage [\#1559](https://github.com/ably/ably-cocoa/issues/1559) +- Remove extra callback with `setSuspended` [\#1550](https://github.com/ably/ably-cocoa/issues/1550) +- Make sure we can build the library and run the tests using Xcode 14 [\#1523](https://github.com/ably/ably-cocoa/issues/1523) + ## [1.2.19](https://github.com/ably/ably-cocoa/tree/1.2.19) [Full Changelog](https://github.com/ably/ably-cocoa/compare/1.2.18...1.2.19) diff --git a/README.md b/README.md index 3de763a36..728124ed8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ You can install Ably for iOS and macOS through Package Manager, CocoaPods, Carth - [This apple guide](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) explains the steps in more detail. - To install the `ably-cocoa` package in another **Swift Package**, then add the following to your `Package.Swift`: ```swift - .package(url: "https://github.com/ably/ably-cocoa", from: "1.2.19"), + .package(url: "https://github.com/ably/ably-cocoa", from: "1.2.20"), ``` ### Installing through [CocoaPods](https://cocoapods.org/) @@ -104,7 +104,7 @@ If you see, for example, a `dyld: Library not loaded: @rpath/AblyDeltaCodec.fram ### Manual installation -1. Get the code from GitHub [from the release page](https://github.com/ably/ably-cocoa/releases/tag/1.2.19), or clone it to get the latest, unstable and possibly underdocumented version: `git clone git@github.com:ably/ably-cocoa.git` +1. Get the code from GitHub [from the release page](https://github.com/ably/ably-cocoa/releases/tag/1.2.20), or clone it to get the latest, unstable and possibly underdocumented version: `git clone git@github.com:ably/ably-cocoa.git` 2. Drag the directory `ably-cocoa/ably-cocoa` into your project as a group. 3. Ably depends on our [MessagePack Fork](https://github.com/ably-forks/msgpack-objective-C) 0.2.0; get it [from the releases page](https://github.com/ably-forks/msgpack-objective-C/releases/tag/0.2.0-ably-1) and link it into your project. diff --git a/Scripts/jazzy.sh b/Scripts/jazzy.sh index fa7222e44..2744c24ca 100755 --- a/Scripts/jazzy.sh +++ b/Scripts/jazzy.sh @@ -7,7 +7,7 @@ jazzy \ --objc \ --clean \ --author Ably \ - --module-version 1.2.19 \ + --module-version 1.2.20 \ --umbrella-header Source/include/Ably/Ably.h \ --framework-root Source \ --module Ably \ diff --git a/Source/ARTClientInformation.m b/Source/ARTClientInformation.m index 469c0bdea..ef093613f 100644 --- a/Source/ARTClientInformation.m +++ b/Source/ARTClientInformation.m @@ -6,7 +6,7 @@ #import NSString *const ARTClientInformationAgentNotVersioned = @"ARTClientInformationAgentNotVersioned"; -NSString *const ARTClientInformation_libraryVersion = @"1.2.19"; +NSString *const ARTClientInformation_libraryVersion = @"1.2.20"; static NSString *const _libraryName = @"ably-cocoa"; // NSOperatingSystemVersion has NSInteger as version components for some reason, so mitigate it here. diff --git a/Source/ARTTypes.m b/Source/ARTTypes.m index 935f45150..990d544a0 100644 --- a/Source/ARTTypes.m +++ b/Source/ARTTypes.m @@ -29,7 +29,7 @@ NSTimeInterval millisecondsToTimeInterval(uint64_t msecs) { return ((NSTimeInterval)msecs) / 1000; } -NSString *generateNonce() { +NSString *generateNonce(void) { // Generate two random numbers up to 8 digits long and concatenate them to produce a 16 digit random number NSUInteger r1 = arc4random_uniform(100000000); NSUInteger r2 = arc4random_uniform(100000000); diff --git a/Source/SocketRocket/ARTSRWebSocket.m b/Source/SocketRocket/ARTSRWebSocket.m index a09bba345..f6d733a6c 100644 --- a/Source/SocketRocket/ARTSRWebSocket.m +++ b/Source/SocketRocket/ARTSRWebSocket.m @@ -45,7 +45,7 @@ #error SocketRocket must be compiled with ARC enabled #endif -__attribute__((used)) static void importCategories() +__attribute__((used)) static void importCategories(void) { import_NSURLRequest_ARTSRWebSocket(); import_NSRunLoop_ARTSRWebSocket(); diff --git a/Source/SocketRocket/NSRunLoop+ARTSRWebSocket.m b/Source/SocketRocket/NSRunLoop+ARTSRWebSocket.m index 9c6641e42..8b98478d8 100644 --- a/Source/SocketRocket/NSRunLoop+ARTSRWebSocket.m +++ b/Source/SocketRocket/NSRunLoop+ARTSRWebSocket.m @@ -15,7 +15,7 @@ #import "ARTSRRunLoopThread.h" // Required for object file to always be linked. -void import_NSRunLoop_ARTSRWebSocket() { } +void import_NSRunLoop_ARTSRWebSocket(void) { } @implementation NSRunLoop (ARTSRWebSocket) diff --git a/Source/SocketRocket/NSURLRequest+ARTSRWebSocket.m b/Source/SocketRocket/NSURLRequest+ARTSRWebSocket.m index b26d30e17..77a206963 100644 --- a/Source/SocketRocket/NSURLRequest+ARTSRWebSocket.m +++ b/Source/SocketRocket/NSURLRequest+ARTSRWebSocket.m @@ -13,7 +13,7 @@ #import "NSURLRequest+ARTSRWebSocketPrivate.h" // Required for object file to always be linked. -void import_NSURLRequest_ARTSRWebSocket() { } +void import_NSURLRequest_ARTSRWebSocket(void) { } NS_ASSUME_NONNULL_BEGIN diff --git a/Test/Test Utilities/NSObject+TestSuite.swift b/Test/Test Utilities/NSObject+TestSuite.swift index 681bea93a..8a5683e69 100644 --- a/Test/Test Utilities/NSObject+TestSuite.swift +++ b/Test/Test Utilities/NSObject+TestSuite.swift @@ -8,12 +8,4 @@ extension NSObject { return try? self.aspect_hook(selector, with: AspectOptions(), usingBlock: unsafeBitCast(block, to: AnyObject.self)) } - /// Replace identified class method with a block of code. - class func testSuite_replaceClassMethod(_ selector: Selector, code: @escaping ()->()) -> AspectToken? { - let block: @convention(block) (AspectInfo) -> Void = { _ in - code() - } - return try? self.aspect_hook(selector, with: .positionInstead, usingBlock: unsafeBitCast(block, to: AnyObject.self)) - } - } diff --git a/Test/Test Utilities/TestProxyTransportFactory.swift b/Test/Test Utilities/TestProxyTransportFactory.swift index f5c089d62..4588f4850 100644 --- a/Test/Test Utilities/TestProxyTransportFactory.swift +++ b/Test/Test Utilities/TestProxyTransportFactory.swift @@ -8,8 +8,9 @@ class TestProxyTransportFactory: RealtimeTransportFactory { var networkConnectEvent: ((ARTRealtimeTransport, URL) -> Void)? func transport(withRest rest: ARTRestInternal, options: ARTClientOptions, resumeKey: String?, logger: InternalLog) -> ARTRealtimeTransport { - let webSocketFactory = DefaultWebSocketFactory() - return TestProxyTransport( + let webSocketFactory = WebSocketFactory() + + let testProxyTransport = TestProxyTransport( factory: self, rest: rest, options: options, @@ -17,5 +18,33 @@ class TestProxyTransportFactory: RealtimeTransportFactory { logger: logger, webSocketFactory: webSocketFactory ) + + webSocketFactory.testProxyTransport = testProxyTransport + + return testProxyTransport + } + + private class WebSocketFactory: Ably.WebSocketFactory { + weak var testProxyTransport: TestProxyTransport? + + func createWebSocket(with request: URLRequest, logger: InternalLog?) -> ARTWebSocket { + let webSocket = WebSocket(urlRequest: request, logger: logger) + webSocket.testProxyTransport = testProxyTransport + + return webSocket + } + } + + private class WebSocket: ARTSRWebSocket { + weak var testProxyTransport: TestProxyTransport? + + override func open() { + guard let testProxyTransport else { + preconditionFailure("Tried to fetch testProxyTransport but it's already been deallocated") + } + if !testProxyTransport.handleWebSocketOpen() { + super.open() + } + } } } diff --git a/Test/Test Utilities/TestUtilities.swift b/Test/Test Utilities/TestUtilities.swift index 9318ecb4e..cad26a254 100644 --- a/Test/Test Utilities/TestUtilities.swift +++ b/Test/Test Utilities/TestUtilities.swift @@ -1154,6 +1154,24 @@ class TestProxyTransport: ARTWebSocketTransport { private var callbackBeforeIncomingMessageModifier: ((ARTProtocolMessage) -> ARTProtocolMessage?)? private var callbackAfterIncomingMessageModifier: ((ARTProtocolMessage) -> ARTProtocolMessage?)? + // Represents a request to replace the implementation of a method. + private class Hook { + private var implementation: () -> Void + + init(implementation: @escaping () -> Void) { + self.implementation = implementation + } + + func performImplementation() -> Void { + implementation() + } + } + + /// The active request, if any, to replace the implementation of the ARTWebSocket#open method for all WebSocket objects created by this transport. Access must be synchronised using webSocketOpenHookSemaphore. + private var webSocketOpenHook: Hook? + /// Used for synchronising access to webSocketOpenHook. + private let webSocketOpenHookSempahore = DispatchSemaphore(value: 1) + func setListenerBeforeProcessingIncomingMessage(_ callback: ((ARTProtocolMessage) -> Void)?) { queue.sync { self.callbackBeforeProcessingIncomingMessage = callback @@ -1216,9 +1234,40 @@ class TestProxyTransport: ARTWebSocketTransport { performNetworkConnectEvent() } + private func addWebSocketOpenHook(withImplementation implementation: @escaping () -> Void) -> Hook { + webSocketOpenHookSempahore.wait() + let hook = Hook(implementation: implementation) + webSocketOpenHook = hook + webSocketOpenHookSempahore.signal() + return hook + } + + private func removeWebSocketOpenHook(_ hook: Hook) { + webSocketOpenHookSempahore.wait() + if (webSocketOpenHook === hook) { + webSocketOpenHook = nil + } + webSocketOpenHookSempahore.signal() + } + + /// If this transport has been configured with a replacement implementation of ARTWebSocket#open, then this performs that implementation and returns `true`. Else, returns `false`. + func handleWebSocketOpen() -> Bool { + let hook: Hook? + webSocketOpenHookSempahore.wait() + hook = webSocketOpenHook + webSocketOpenHookSempahore.signal() + + if let hook { + hook.performImplementation() + return true + } else { + return false + } + } + private func setupFakeNetworkResponse(_ networkResponse: FakeNetworkResponse) { - var hook: AspectToken? - hook = ARTSRWebSocket.testSuite_replaceClassMethod(#selector(ARTSRWebSocket.open)) { + var hook: Hook? + hook = addWebSocketOpenHook { if self.factory.fakeNetworkResponse == nil { return } @@ -1226,7 +1275,9 @@ class TestProxyTransport: ARTWebSocketTransport { func performFakeConnectionError(_ secondsForDelay: TimeInterval, error: ARTRealtimeTransportError) { self.queue.asyncAfter(deadline: .now() + secondsForDelay) { self.delegate?.realtimeTransportFailed(self, withError: error) - hook?.remove() + if let hook { + self.removeWebSocketOpenHook(hook) + } } } diff --git a/Test/Tests/ARTDefaultTests.swift b/Test/Tests/ARTDefaultTests.swift index 913c759c7..e22a39183 100644 --- a/Test/Tests/ARTDefaultTests.swift +++ b/Test/Tests/ARTDefaultTests.swift @@ -6,6 +6,6 @@ class ARTDefaultTests: XCTestCase { func testVersions() { XCTAssertEqual(ARTDefault.apiVersion(), "2") - XCTAssertEqual(ARTDefault.libraryVersion(), "1.2.19") + XCTAssertEqual(ARTDefault.libraryVersion(), "1.2.20") } } diff --git a/Test/Tests/AuthTests.swift b/Test/Tests/AuthTests.swift index e53b51baa..8fe2dc92b 100644 --- a/Test/Tests/AuthTests.swift +++ b/Test/Tests/AuthTests.swift @@ -832,7 +832,9 @@ class AuthTests: XCTestCase { options.authCallback = { _, completion in getTestTokenDetails(for: test, completion: completion) } - options.testOptions.realtimeRequestTimeout = 0.5 + + // This needs to be sufficiently long such that we can expect to receive a CONNECTED ProtocolMessage within this duration after starting a connection attempt (there's no "correct" value since it depends on network conditions, but 1.5s seemed to work locally and in CI at time of writing). But we also don't want it to be longer than necessary since that would impact test execution time. + options.testOptions.realtimeRequestTimeout = 1.5 let realtime = ARTRealtime(options: options) defer { realtime.dispose(); realtime.close() } diff --git a/Test/Tests/ClientInformationTests.swift b/Test/Tests/ClientInformationTests.swift index 136008706..18222fa1b 100644 --- a/Test/Tests/ClientInformationTests.swift +++ b/Test/Tests/ClientInformationTests.swift @@ -9,7 +9,7 @@ final class ClientInformationTests: XCTestCase { XCTAssertEqual(agents.keys.count, 2) - XCTAssertEqual(agents["ably-cocoa"], "1.2.19") + XCTAssertEqual(agents["ably-cocoa"], "1.2.20") #if os(iOS) XCTAssertTrue(agents.keys.contains("iOS")) @@ -27,7 +27,7 @@ final class ClientInformationTests: XCTestCase { // CR3, CR3b func testAgentIdentifierWithAdditionalAgents_withNilAdditionalAgents() { let expectedIdentifier = [ - "ably-cocoa/1.2.19", + "ably-cocoa/1.2.20", ARTDefault.platformAgent() ].sorted().joined(separator: " ") @@ -42,7 +42,7 @@ final class ClientInformationTests: XCTestCase { ] let expectedIdentifier = [ - "ably-cocoa/1.2.19", + "ably-cocoa/1.2.20", "demolib/0.0.1", "morelib", ARTDefault.platformAgent() diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index f83590b37..a0c1b3f82 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -2781,7 +2781,7 @@ class RealtimeClientChannelTests: XCTestCase { channelOne.publish("i", data: ["expectedBundle": 5], clientId: "bar") channelOne.publish("j", data: ["expectedBundle": 6]) // RTL6d1 - channelOne.publish("k", data: ["expectedBundle": 7, "moreData": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]) + channelOne.publish("k", data: ["expectedBundle": 7, "moreData": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] as [String : Any]) channelOne.publish("l", data: ["expectedBundle": 8]) // RTL6d7 channelOne.publish([ARTMessage(id: "bundle_m", name: "m", data: ["expectedBundle": 9])]) diff --git a/Test/Tests/RealtimeClientConnectionTests.swift b/Test/Tests/RealtimeClientConnectionTests.swift index 150b89574..5a1a62b9b 100644 --- a/Test/Tests/RealtimeClientConnectionTests.swift +++ b/Test/Tests/RealtimeClientConnectionTests.swift @@ -381,7 +381,7 @@ class RealtimeClientConnectionTests: XCTestCase { done() case .connected: if let transport = client.internal.transport as? TestProxyTransport, let query = transport.lastUrl?.query { - expect(query).to(haveParam("agent", hasPrefix: "ably-cocoa/1.2.19")) + expect(query).to(haveParam("agent", hasPrefix: "ably-cocoa/1.2.20")) } else { XCTFail("MockTransport isn't working") } @@ -4407,7 +4407,8 @@ class RealtimeClientConnectionTests: XCTestCase { func test__011__Connection__should_disconnect_the_transport_when_no_activity_exist() throws { let test = Test() let options = try AblyTests.commonAppSetup(for: test) - let realtimeRequestTimeout = 0.5 + // This needs to be sufficiently long such that we can expect to receive a CONNECTED ProtocolMessage within this duration after starting a connection attempt (there's no "correct" value since it depends on network conditions, but 1.5s seemed to work locally and in CI at time of writing). But we also don't want it to be longer than necessary since that would impact test execution time. + let realtimeRequestTimeout = 1.5 options.testOptions.realtimeRequestTimeout = realtimeRequestTimeout let client = AblyTests.newRealtime(options).client defer { client.dispose(); client.close() } @@ -4462,7 +4463,7 @@ class RealtimeClientConnectionTests: XCTestCase { } } - XCTAssertEqual(expectedInactivityTimeout, 3.5) + XCTAssertEqual(expectedInactivityTimeout, 4.5) XCTAssertEqual(client.internal.maxIdleInterval, 3.0) } diff --git a/Test/Tests/RestClientTests.swift b/Test/Tests/RestClientTests.swift index 45ac31610..f59ffdedb 100644 --- a/Test/Tests/RestClientTests.swift +++ b/Test/Tests/RestClientTests.swift @@ -1734,7 +1734,7 @@ class RestClientTests: XCTestCase { let headerAgent = testHTTPExecutor.requests.first!.allHTTPHeaderFields?["Ably-Agent"] let ablyAgent = ARTClientInformation.agentIdentifier(withAdditionalAgents: options.agents) XCTAssertEqual(headerAgent, ablyAgent) - XCTAssertTrue(headerAgent!.hasPrefix("ably-cocoa/1.2.19")) + XCTAssertTrue(headerAgent!.hasPrefix("ably-cocoa/1.2.20")) done() } } diff --git a/Test/Tests/UtilitiesTests.swift b/Test/Tests/UtilitiesTests.swift index e1d2a642e..ba9955c00 100644 --- a/Test/Tests/UtilitiesTests.swift +++ b/Test/Tests/UtilitiesTests.swift @@ -46,12 +46,12 @@ class UtilitiesTests: XCTestCase { func test__001__Utilities__JSON_Encoder__should_decode_a_protocol_message_that_has_an_error_without_a_message() throws { beforeEach__Utilities__JSON_Encoder() - let jsonObject: NSDictionary = [ + let jsonObject: [String : Any] = [ "action": 9, "error": [ "code": 40142, "statusCode": "401", - ], + ] as [String : Any], ] let data = try JSONSerialization.data(withJSONObject: jsonObject, options: []) guard let protocolMessage = try? jsonEncoder.decodeProtocolMessage(data) else { diff --git a/Version.xcconfig b/Version.xcconfig index 251a8664c..50e1a8d30 100644 --- a/Version.xcconfig +++ b/Version.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 1.2.19 +CURRENT_PROJECT_VERSION = 1.2.20