From bfaaba170bafb5ae71583102529e00989354bfe1 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Thu, 12 Sep 2024 19:30:33 +0200 Subject: [PATCH 1/6] By analogy with `ARTRealtimePresenceQuery` (which causes warning in Xcode 16) I've added `NS_SWIFT_SENDABLE` to other query types as well. Also added NS_SWIFT_SENDABLE to `ARTChannelProperties`. --- Ably.xcodeproj/project.pbxproj | 16 +++++ Source/ARTBaseQuery.m | 4 ++ Source/ARTDataQuery.m | 76 +++++++++++++++++++- Source/ARTRealtimePresence.m | 19 ++++- Source/ARTRest.m | 2 + Source/ARTRestChannel.m | 2 + Source/ARTRestPresence.m | 49 ++++++++++++- Source/ARTStats.m | 17 ++++- Source/Ably.modulemap | 1 + Source/include/Ably/ARTBaseQuery.h | 8 +++ Source/include/Ably/ARTDataQuery.h | 6 +- Source/include/Ably/ARTRealtimeChannel.h | 1 + Source/include/Ably/ARTRealtimePresence.h | 1 + Source/include/Ably/ARTRestPresence.h | 4 +- Source/include/Ably/ARTStats.h | 1 + Test/Tests/RealtimeClientChannelTests.swift | 6 ++ Test/Tests/RealtimeClientPresenceTests.swift | 13 +++- Test/Tests/RestClientChannelTests.swift | 36 ++++++++-- Test/Tests/RestClientPresenceTests.swift | 40 ++++++++--- Test/Tests/RestClientStatsTests.swift | 6 ++ 20 files changed, 287 insertions(+), 21 deletions(-) create mode 100755 Source/ARTBaseQuery.m create mode 100755 Source/include/Ably/ARTBaseQuery.h diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index d0aec78ec..6970ef7a2 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -299,6 +299,12 @@ 844B9CCF2C807BC400A260E8 /* ARTDeviceDetails+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 844B9CCE2C807BC400A260E8 /* ARTDeviceDetails+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 844B9CD02C807BC400A260E8 /* ARTDeviceDetails+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 844B9CCE2C807BC400A260E8 /* ARTDeviceDetails+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 844B9CD12C807BC400A260E8 /* ARTDeviceDetails+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 844B9CCE2C807BC400A260E8 /* ARTDeviceDetails+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 845381F42C930A8C0085834E /* ARTBaseQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 845381F32C930A8C0085834E /* ARTBaseQuery.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 845381F52C930A8C0085834E /* ARTBaseQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 845381F32C930A8C0085834E /* ARTBaseQuery.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 845381F62C930A8C0085834E /* ARTBaseQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 845381F32C930A8C0085834E /* ARTBaseQuery.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 845381F82C930B9D0085834E /* ARTBaseQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 845381F72C930B9D0085834E /* ARTBaseQuery.m */; }; + 845381F92C930B9D0085834E /* ARTBaseQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 845381F72C930B9D0085834E /* ARTBaseQuery.m */; }; + 845381FA2C930B9D0085834E /* ARTBaseQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 845381F72C930B9D0085834E /* ARTBaseQuery.m */; }; 848ED97326E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; 848ED97426E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; 848ED97526E50D0F0087E800 /* ObjcppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848ED97226E50D0F0087E800 /* ObjcppTest.mm */; settings = {COMPILER_FLAGS = "-fmodules"; }; }; @@ -1245,6 +1251,8 @@ 8412FDE32661AC37001FE9E6 /* msgpack.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = msgpack.xcframework; path = Carthage/Build/msgpack.xcframework; sourceTree = ""; }; 8412FDF42661AC7B001FE9E6 /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = ""; }; 844B9CCE2C807BC400A260E8 /* ARTDeviceDetails+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ARTDeviceDetails+Private.h"; path = "PrivateHeaders/Ably/ARTDeviceDetails+Private.h"; sourceTree = ""; }; + 845381F32C930A8C0085834E /* ARTBaseQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTBaseQuery.h; path = include/Ably/ARTBaseQuery.h; sourceTree = ""; }; + 845381F72C930B9D0085834E /* ARTBaseQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTBaseQuery.m; sourceTree = ""; }; 848ED97226E50D0F0087E800 /* ObjcppTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjcppTest.mm; sourceTree = ""; }; 850BFB4A1B79323C009D0ADD /* ARTPaginatedResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTPaginatedResult.h; path = include/Ably/ARTPaginatedResult.h; sourceTree = ""; }; 850BFB4B1B79323C009D0ADD /* ARTPaginatedResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPaginatedResult.m; sourceTree = ""; }; @@ -2125,6 +2133,8 @@ 96BF61571A35B52C004CF2B3 /* ARTHttp.m */, D7588AF11BFF91B800BB8279 /* ARTURLSessionServerTrust.h */, D7588AF21BFF91B800BB8279 /* ARTURLSessionServerTrust.m */, + 845381F32C930A8C0085834E /* ARTBaseQuery.h */, + 845381F72C930B9D0085834E /* ARTBaseQuery.m */, D746AE1A1BBB5207003ECEF8 /* ARTDataQuery.h */, D746AE1B1BBB5207003ECEF8 /* ARTDataQuery+Private.h */, D746AE1C1BBB5207003ECEF8 /* ARTDataQuery.m */, @@ -2232,6 +2242,7 @@ EB91213E1CA0AD6600BA0A40 /* ARTMsgPackEncoder.h in Headers */, 96A507BD1A3791490077CDF8 /* ARTRealtime.h in Headers */, 21088DC32A5354F10033C722 /* ARTConnectRetryState.h in Headers */, + 845381F42C930A8C0085834E /* ARTBaseQuery.h in Headers */, EB5E058D1C77027600A48B39 /* ARTCrypto+Private.h in Headers */, 2132C2F529D5BE05000C4355 /* ARTRetrySequence.h in Headers */, D72C67DF201AB74000978EBB /* ARTPushActivationStateMachine+Private.h in Headers */, @@ -2472,6 +2483,7 @@ D710D4A921949ADF008F54AD /* ARTRestChannels.h in Headers */, D710D4D821949BF9008F54AD /* ARTRealtimePresence.h in Headers */, D710D48F21949AAE008F54AD /* ARTRest.h in Headers */, + 845381F52C930A8C0085834E /* ARTBaseQuery.h in Headers */, D710D5B821949D4F008F54AD /* ARTAuthOptions+Private.h in Headers */, 2124B79429DB13C400AD8361 /* ARTInternalLog.h in Headers */, D710D51D21949C42008F54AD /* ARTDeviceStorage.h in Headers */, @@ -2642,6 +2654,7 @@ D710D4AF21949AE0008F54AD /* ARTRestChannels.h in Headers */, D710D4E821949BFB008F54AD /* ARTRealtimePresence.h in Headers */, D710D49121949AAF008F54AD /* ARTRest.h in Headers */, + 845381F62C930A8C0085834E /* ARTBaseQuery.h in Headers */, D710D5C821949D50008F54AD /* ARTAuthOptions+Private.h in Headers */, 2124B79529DB13C400AD8361 /* ARTInternalLog.h in Headers */, D710D52F21949C44008F54AD /* ARTDeviceStorage.h in Headers */, @@ -3177,6 +3190,7 @@ D7DF738B1EA645300013CD36 /* ARTLocalDeviceStorage.m in Sources */, D7B621911E4A6E0200684474 /* ARTPush.m in Sources */, D70C36C6233E6831002FD6E3 /* ARTFormEncode.m in Sources */, + 845381F82C930B9D0085834E /* ARTBaseQuery.m in Sources */, D7AE18D31E5B410F00478D82 /* ARTPushChannelSubscriptions.m in Sources */, 1CD8DCA01B1C7315007EAF36 /* ARTDefault.m in Sources */, 211A610729DA05D700D169C5 /* ARTAttachRequestParams.m in Sources */, @@ -3417,6 +3431,7 @@ D710D5D621949D78008F54AD /* ARTChannel.m in Sources */, D70C36C7233E6831002FD6E3 /* ARTFormEncode.m in Sources */, D710D5D121949D78008F54AD /* ARTAuthDetails.m in Sources */, + 845381F92C930B9D0085834E /* ARTBaseQuery.m in Sources */, D710D4EC21949C0D008F54AD /* ARTRealtime.m in Sources */, D710D4ED21949C0D008F54AD /* ARTRealtimeChannel.m in Sources */, 211A610829DA05D700D169C5 /* ARTAttachRequestParams.m in Sources */, @@ -3541,6 +3556,7 @@ D70C36C8233E6831002FD6E3 /* ARTFormEncode.m in Sources */, D710D5F721949D79008F54AD /* ARTAuthDetails.m in Sources */, D710D4FC21949C0E008F54AD /* ARTRealtime.m in Sources */, + 845381FA2C930B9D0085834E /* ARTBaseQuery.m in Sources */, D710D4FD21949C0E008F54AD /* ARTRealtimeChannel.m in Sources */, D710D65621949E77008F54AD /* ARTNSDictionary+ARTDictionaryUtil.m in Sources */, 211A610929DA05D700D169C5 /* ARTAttachRequestParams.m in Sources */, diff --git a/Source/ARTBaseQuery.m b/Source/ARTBaseQuery.m new file mode 100755 index 000000000..a3a3c35bf --- /dev/null +++ b/Source/ARTBaseQuery.m @@ -0,0 +1,4 @@ +#import "ARTBaseQuery.h" + +@implementation ARTBaseQuery +@end diff --git a/Source/ARTDataQuery.m b/Source/ARTDataQuery.m index af98d8da8..cb225333e 100755 --- a/Source/ARTDataQuery.m +++ b/Source/ARTDataQuery.m @@ -1,7 +1,12 @@ #import "ARTDataQuery+Private.h" #import "ARTRealtimeChannel+Private.h" -@implementation ARTDataQuery +@implementation ARTDataQuery { + NSDate *_start; + NSDate *_end; + uint16_t _limit; + ARTQueryDirection _direction; +} - (instancetype)init { if (self = [super init]) { @@ -38,9 +43,63 @@ - (NSMutableArray *)asQueryItems:(NSError *_Nullable*)error { return items; } +- (NSDate *)start { + return _start; +} + +- (void)setStart:(NSDate *)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _start = value; +} + +- (NSDate *)end { + return _end; +} + +- (void)setEnd:(NSDate *)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _end = value; +} + +- (uint16_t)limit { + return _limit; +} + +- (void)setLimit:(uint16_t)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _limit = value; +} + +- (ARTQueryDirection)direction { + return _direction; +} + +- (void)setDirection:(ARTQueryDirection)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _direction = value; +} + @end -@implementation ARTRealtimeHistoryQuery +@implementation ARTRealtimeHistoryQuery { + BOOL _untilAttach; +} - (NSMutableArray *)asQueryItems:(NSError **)errorPtr { NSMutableArray *items = [super asQueryItems:errorPtr]; @@ -58,4 +117,17 @@ - (NSMutableArray *)asQueryItems:(NSError **)errorPtr { return items; } +- (BOOL)untilAttach { + return _untilAttach; +} + +- (void)setUntilAttach:(BOOL)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _untilAttach = value; +} + @end diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index 8d27e63dc..dfbf79434 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -19,7 +19,9 @@ #pragma mark - ARTRealtimePresenceQuery -@implementation ARTRealtimePresenceQuery +@implementation ARTRealtimePresenceQuery { + BOOL _waitForSync; +} - (instancetype)initWithLimit:(NSUInteger)limit clientId:(NSString *)clientId connectionId:(NSString *)connectionId { self = [super initWithLimit:limit clientId:clientId connectionId:connectionId]; @@ -29,6 +31,19 @@ - (instancetype)initWithLimit:(NSUInteger)limit clientId:(NSString *)clientId co return self; } +- (BOOL)waitForSync { + return _waitForSync; +} + +- (void)setWaitForSync:(BOOL)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _waitForSync = value; +} + @end @implementation ARTRealtimePresence { @@ -221,6 +236,8 @@ - (void)get:(ARTRealtimePresenceQuery *)query callback:(ARTPresenceMessagesCallb }; } + query.frozen = true; + dispatch_async(_queue, ^{ switch (self->_channel.state_nosync) { case ARTRealtimeChannelDetached: diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 36df56b52..f17b2ae18 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -667,6 +667,8 @@ - (BOOL)stats:(ARTStatsQuery *)query callback:(ARTPaginatedStatsCallback)callbac }); }; } + + query.frozen = true; if (query.limit > 1000) { if (errorPtr) { diff --git a/Source/ARTRestChannel.m b/Source/ARTRestChannel.m index d00db872a..4dab8cc03 100644 --- a/Source/ARTRestChannel.m +++ b/Source/ARTRestChannel.m @@ -161,6 +161,8 @@ - (BOOL)history:(ARTDataQuery *)query callback:(ARTPaginatedMessagesCallback)cal }); }; } + + query.frozen = true; __block BOOL ret; dispatch_sync(_queue, ^{ diff --git a/Source/ARTRestPresence.m b/Source/ARTRestPresence.m index 7bac83682..01ce71620 100644 --- a/Source/ARTRestPresence.m +++ b/Source/ARTRestPresence.m @@ -14,7 +14,11 @@ #import "ARTChannel.h" #import "ARTDataQuery.h" -@implementation ARTPresenceQuery +@implementation ARTPresenceQuery { + NSUInteger _limit; + NSString *_clientId; + NSString *_connectionId; +} - (instancetype)init { return [self initWithClientId:nil connectionId:nil]; @@ -49,6 +53,45 @@ - (NSMutableArray *)asQueryItems { return items; } +- (NSUInteger)limit { + return _limit; +} + +- (void)setLimit:(NSUInteger)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _limit = value; +} + +- (NSString *)clientId { + return _clientId; +} + +- (void)setClientId:(NSString *)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _clientId = value; +} + +- (NSString *)connectionId { + return _connectionId; +} + +- (void)setConnectionId:(NSString *)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _connectionId = value; +} + @end @implementation ARTRestPresence { @@ -130,6 +173,8 @@ - (BOOL)get:(ARTPresenceQuery *)query callback:(ARTPaginatedPresenceCallback)cal }; } + query.frozen = true; + if (query.limit > 1000) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain @@ -175,6 +220,8 @@ - (BOOL)history:(ARTDataQuery *)query callback:(ARTPaginatedPresenceCallback)cal }; } + query.frozen = true; + if (query.limit > 1000) { if (errorPtr) { *errorPtr = [NSError errorWithDomain:ARTAblyErrorDomain diff --git a/Source/ARTStats.m b/Source/ARTStats.m index 0383ca163..40d222a38 100644 --- a/Source/ARTStats.m +++ b/Source/ARTStats.m @@ -1,7 +1,9 @@ #import "ARTStats.h" #import "ARTDataQuery+Private.h" -@implementation ARTStatsQuery +@implementation ARTStatsQuery { + ARTStatsGranularity _unit; +} - (instancetype)init { if (self = [super init]) { @@ -34,6 +36,19 @@ - (NSMutableArray *)asQueryItems:(NSError **)error { return items; } +- (ARTStatsGranularity)unit { + return _unit; +} + +- (void)setUnit:(ARTStatsGranularity)value { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } + _unit = value; +} + @end @implementation ARTStatsMessageCount diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index fb003b9ad..5086c62e6 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -14,6 +14,7 @@ framework module Ably { header "ARTChannels+Private.h" header "ARTConnection+Private.h" header "ARTConnectionDetails+Private.h" + header "ARTBaseQuery.h" header "ARTDataQuery+Private.h" header "ARTDefault+Private.h" header "ARTEventEmitter+Private.h" diff --git a/Source/include/Ably/ARTBaseQuery.h b/Source/include/Ably/ARTBaseQuery.h new file mode 100755 index 000000000..189685c6a --- /dev/null +++ b/Source/include/Ably/ARTBaseQuery.h @@ -0,0 +1,8 @@ +#import + +/// :nodoc: +@interface ARTBaseQuery : NSObject + +@property (nonatomic, getter=isFrozen) BOOL frozen; + +@end diff --git a/Source/include/Ably/ARTDataQuery.h b/Source/include/Ably/ARTDataQuery.h index 829d994d7..5a51b4586 100755 --- a/Source/include/Ably/ARTDataQuery.h +++ b/Source/include/Ably/ARTDataQuery.h @@ -1,5 +1,5 @@ #import - +#import #import NS_ASSUME_NONNULL_BEGIN @@ -14,7 +14,8 @@ typedef NS_ENUM(NSUInteger, ARTQueryDirection) { /** This object is used for providing parameters into methods with paginated results. */ -@interface ARTDataQuery : NSObject +NS_SWIFT_SENDABLE +@interface ARTDataQuery : ARTBaseQuery /** * The time from which the data items are retrieved. @@ -41,6 +42,7 @@ typedef NS_ENUM(NSUInteger, ARTQueryDirection) { /** This object is used for providing parameters into `ARTRealtimePresence`'s methods with paginated results. */ +NS_SWIFT_SENDABLE @interface ARTRealtimeHistoryQuery : ARTDataQuery /** diff --git a/Source/include/Ably/ARTRealtimeChannel.h b/Source/include/Ably/ARTRealtimeChannel.h index 3ec95f04b..cca12dfc8 100644 --- a/Source/include/Ably/ARTRealtimeChannel.h +++ b/Source/include/Ably/ARTRealtimeChannel.h @@ -154,6 +154,7 @@ ART_EMBED_INTERFACE_EVENT_EMITTER(ARTChannelEvent, ARTChannelStateChange *) /** * Describes the properties of the channel state. */ +NS_SWIFT_SENDABLE @interface ARTChannelProperties : NSObject /** * Starts unset when a channel is instantiated, then updated with the `channelSerial` from each `ARTChannelEventAttached` event that matches the channel. Used as the value for `ARTRealtimeHistoryQuery.untilAttach`. diff --git a/Source/include/Ably/ARTRealtimePresence.h b/Source/include/Ably/ARTRealtimePresence.h index 3bb3629a6..003916b2b 100644 --- a/Source/include/Ably/ARTRealtimePresence.h +++ b/Source/include/Ably/ARTRealtimePresence.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN /** This object is used for providing parameters into `ARTRealtimePresence`'s methods with paginated results. */ +NS_SWIFT_SENDABLE @interface ARTRealtimePresenceQuery : ARTPresenceQuery /** diff --git a/Source/include/Ably/ARTRestPresence.h b/Source/include/Ably/ARTRestPresence.h index f81f66fb9..4d84ebf44 100644 --- a/Source/include/Ably/ARTRestPresence.h +++ b/Source/include/Ably/ARTRestPresence.h @@ -2,6 +2,7 @@ #import #import +#import @class ARTRestChannel; @@ -10,7 +11,8 @@ NS_ASSUME_NONNULL_BEGIN /** This object is used for providing parameters into `ARTRestPresence`'s methods with paginated results. */ -@interface ARTPresenceQuery : NSObject +NS_SWIFT_SENDABLE +@interface ARTPresenceQuery : ARTBaseQuery /** * An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. diff --git a/Source/include/Ably/ARTStats.h b/Source/include/Ably/ARTStats.h index 1f2c31dc1..9f5aa6bff 100644 --- a/Source/include/Ably/ARTStats.h +++ b/Source/include/Ably/ARTStats.h @@ -30,6 +30,7 @@ typedef NS_ENUM(NSUInteger, ARTStatsGranularity) { /** This object is used for providing parameters into `ARTStats`'s methods with paginated results. */ +NS_SWIFT_SENDABLE @interface ARTStatsQuery : ARTDataQuery /** diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index 548e98ba2..42da4e7e6 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -3661,6 +3661,12 @@ class RealtimeClientChannelTests: XCTestCase { } }.toNot(throwError { err in fail("\(err)"); done() }) } + + let exception1 = tryInObjC { + query.untilAttach = false // frozen + } + XCTAssertNotNil(exception1) + XCTAssertEqual(exception1!.name, NSExceptionName.objectInaccessibleException) } // RTL10c diff --git a/Test/Tests/RealtimeClientPresenceTests.swift b/Test/Tests/RealtimeClientPresenceTests.swift index dc2a545af..ff8904c90 100644 --- a/Test/Tests/RealtimeClientPresenceTests.swift +++ b/Test/Tests/RealtimeClientPresenceTests.swift @@ -3245,6 +3245,11 @@ class RealtimeClientPresenceTests: XCTestCase { let params = ARTRealtimePresenceQuery() params.waitForSync = true channel.presence.get(params, callback: callback) + let exception = tryInObjC { + params.waitForSync = false // frozen + } + XCTAssertNotNil(exception) + XCTAssertEqual(exception!.name, NSExceptionName.objectInaccessibleException) } } @@ -3547,7 +3552,13 @@ class RealtimeClientPresenceTests: XCTestCase { } XCTAssertTrue(restPresenceHistoryMethodWasCalled) restPresenceHistoryMethodWasCalled = false - + + let exception1 = tryInObjC { + queryRealtime.untilAttach = false // frozen + } + XCTAssertNotNil(exception1) + XCTAssertEqual(exception1!.name, NSExceptionName.objectInaccessibleException) + waitUntil(timeout: testTimeout) { done in expect { try channelRealtime.presence.history(queryRealtime) { _, _ in diff --git a/Test/Tests/RestClientChannelTests.swift b/Test/Tests/RestClientChannelTests.swift index 10513784c..8e2f82203 100644 --- a/Test/Tests/RestClientChannelTests.swift +++ b/Test/Tests/RestClientChannelTests.swift @@ -592,6 +592,30 @@ class RestClientChannelTests: XCTestCase { XCTAssertTrue(message.extras == extras) done() } + + let exception1 = tryInObjC { + query.limit = 1000 // frozen + } + XCTAssertNotNil(exception1) + XCTAssertEqual(exception1!.name, NSExceptionName.objectInaccessibleException) + + let exception2 = tryInObjC { + query.start = Date() // frozen + } + XCTAssertNotNil(exception2) + XCTAssertEqual(exception2!.name, NSExceptionName.objectInaccessibleException) + + let exception3 = tryInObjC { + query.end = Date() // frozen + } + XCTAssertNotNil(exception3) + XCTAssertEqual(exception3!.name, NSExceptionName.objectInaccessibleException) + + let exception4 = tryInObjC { + query.direction = .forwards // frozen + } + XCTAssertNotNil(exception4) + XCTAssertEqual(exception4!.name, NSExceptionName.objectInaccessibleException) } } } @@ -1103,9 +1127,12 @@ class RestClientChannelTests: XCTestCase { XCTAssertEqual(error._code, ARTDataQueryError.timestampRange.rawValue) }) - query.direction = .forwards + let query2 = ARTDataQuery() + query2.direction = .forwards + query2.end = query.end + query2.start = query.start - expect { try channel.history(query) { _, _ in } }.to(throwError { (error: Error) in + expect { try channel.history(query2) { _, _ in } }.to(throwError { (error: Error) in XCTAssertEqual(error._code, ARTDataQueryError.timestampRange.rawValue) }) } @@ -1203,8 +1230,9 @@ class RestClientChannelTests: XCTestCase { query.limit = 1001 expect { try channel.history(query, callback: { _, _ in }) }.to(throwError()) - query.limit = 1000 - expect { try channel.history(query, callback: { _, _ in }) }.toNot(throwError()) + let query2 = ARTDataQuery() + query2.limit = 1000 + expect { try channel.history(query2, callback: { _, _ in }) }.toNot(throwError()) } // RSL3, RSP1 diff --git a/Test/Tests/RestClientPresenceTests.swift b/Test/Tests/RestClientPresenceTests.swift index 55a038788..05a6a9233 100644 --- a/Test/Tests/RestClientPresenceTests.swift +++ b/Test/Tests/RestClientPresenceTests.swift @@ -75,9 +75,28 @@ class RestClientPresenceTests: XCTestCase { query.limit = 1001 expect { try channel.presence.get(query, callback: { _, _ in }) }.to(throwError()) + + let exception1 = tryInObjC { + query.limit = 1000 // frozen + } + XCTAssertNotNil(exception1) + XCTAssertEqual(exception1!.name, NSExceptionName.objectInaccessibleException) + + let exception2 = tryInObjC { + query.connectionId = "other" // frozen + } + XCTAssertNotNil(exception2) + XCTAssertEqual(exception2!.name, NSExceptionName.objectInaccessibleException) + + let exception3 = tryInObjC { + query.clientId = "other" // frozen + } + XCTAssertNotNil(exception3) + XCTAssertEqual(exception3!.name, NSExceptionName.objectInaccessibleException) - query.limit = 1000 - expect { try channel.presence.get(query, callback: { _, _ in }) }.toNot(throwError()) + let query2 = ARTPresenceQuery() + query2.limit = 1000 + expect { try channel.presence.get(query2, callback: { _, _ in }) }.toNot(throwError()) } // RSP3a2 @@ -256,11 +275,12 @@ class RestClientPresenceTests: XCTestCase { }.toNot(throwError()) } - query.direction = .forwards + let query2 = ARTDataQuery() + query2.direction = .forwards waitUntil(timeout: testTimeout) { done in expect { - try channel.presence.history(query) { membersPage, error in + try channel.presence.history(query2) { membersPage, error in XCTAssertNil(error) let firstMember = membersPage!.items.first! XCTAssertEqual(firstMember.clientId, "user1") @@ -305,9 +325,10 @@ class RestClientPresenceTests: XCTestCase { }.toNot(throwError()) } - query.limit = 1001 + let query2 = ARTDataQuery() + query2.limit = 1001 - expect { try channel.presence.history(query) { _, _ in } }.to(throwError { (error: Error) in + expect { try channel.presence.history(query2) { _, _ in } }.to(throwError { (error: Error) in XCTAssertEqual(error._code, ARTDataQueryError.limit.rawValue) }) } @@ -430,9 +451,12 @@ class RestClientPresenceTests: XCTestCase { XCTAssertEqual(error._code, ARTDataQueryError.timestampRange.rawValue) }) - query.direction = .forwards + let query2 = ARTDataQuery() + query2.end = query.end + query2.start = query.start + query2.direction = .forwards - expect { try channel.presence.history(query) { _, _ in } }.to(throwError { (error: Error) in + expect { try channel.presence.history(query2) { _, _ in } }.to(throwError { (error: Error) in XCTAssertEqual(error._code, ARTDataQueryError.timestampRange.rawValue) }) } diff --git a/Test/Tests/RestClientStatsTests.swift b/Test/Tests/RestClientStatsTests.swift index 8a4e80688..d0e8e03ac 100644 --- a/Test/Tests/RestClientStatsTests.swift +++ b/Test/Tests/RestClientStatsTests.swift @@ -111,6 +111,12 @@ class RestClientStatsTests: XCTestCase { let result = try queryStats(client, query) XCTAssertEqual(result.items.count, 3) + let exception1 = tryInObjC { + query.unit = .month // frozen + } + XCTAssertNotNil(exception1) + XCTAssertEqual(exception1!.name, NSExceptionName.objectInaccessibleException) + let totalInbound = result.items.reduce(0 as UInt) { $0 + $1.inbound.all.messages.count } From e22ff7c394705b4896956796b59bdb909e656019 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Thu, 12 Sep 2024 22:17:45 +0200 Subject: [PATCH 2/6] Applied coderabbit suggestion for `throwIfFrozen` method. --- Source/ARTBaseQuery.m | 9 +++++++++ Source/ARTDataQuery.m | 30 +++++------------------------- Source/ARTRealtimePresence.m | 6 +----- Source/ARTRestPresence.m | 18 +++--------------- Source/ARTStats.m | 6 +----- Source/include/Ably/ARTBaseQuery.h | 2 ++ 6 files changed, 21 insertions(+), 50 deletions(-) diff --git a/Source/ARTBaseQuery.m b/Source/ARTBaseQuery.m index a3a3c35bf..6a7a6bc7e 100755 --- a/Source/ARTBaseQuery.m +++ b/Source/ARTBaseQuery.m @@ -1,4 +1,13 @@ #import "ARTBaseQuery.h" @implementation ARTBaseQuery + +- (void)throwIfFrozen { + if (self.isFrozen) { + @throw [NSException exceptionWithName:NSObjectInaccessibleException + reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] + userInfo:nil]; + } +} + @end diff --git a/Source/ARTDataQuery.m b/Source/ARTDataQuery.m index cb225333e..cfb58adae 100755 --- a/Source/ARTDataQuery.m +++ b/Source/ARTDataQuery.m @@ -48,11 +48,7 @@ - (NSDate *)start { } - (void)setStart:(NSDate *)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _start = value; } @@ -61,11 +57,7 @@ - (NSDate *)end { } - (void)setEnd:(NSDate *)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _end = value; } @@ -74,11 +66,7 @@ - (uint16_t)limit { } - (void)setLimit:(uint16_t)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _limit = value; } @@ -87,11 +75,7 @@ - (ARTQueryDirection)direction { } - (void)setDirection:(ARTQueryDirection)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _direction = value; } @@ -122,11 +106,7 @@ - (BOOL)untilAttach { } - (void)setUntilAttach:(BOOL)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _untilAttach = value; } diff --git a/Source/ARTRealtimePresence.m b/Source/ARTRealtimePresence.m index dfbf79434..54cebba8a 100644 --- a/Source/ARTRealtimePresence.m +++ b/Source/ARTRealtimePresence.m @@ -36,11 +36,7 @@ - (BOOL)waitForSync { } - (void)setWaitForSync:(BOOL)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _waitForSync = value; } diff --git a/Source/ARTRestPresence.m b/Source/ARTRestPresence.m index 01ce71620..9b6fe07d1 100644 --- a/Source/ARTRestPresence.m +++ b/Source/ARTRestPresence.m @@ -58,11 +58,7 @@ - (NSUInteger)limit { } - (void)setLimit:(NSUInteger)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _limit = value; } @@ -71,11 +67,7 @@ - (NSString *)clientId { } - (void)setClientId:(NSString *)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _clientId = value; } @@ -84,11 +76,7 @@ - (NSString *)connectionId { } - (void)setConnectionId:(NSString *)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _connectionId = value; } diff --git a/Source/ARTStats.m b/Source/ARTStats.m index 40d222a38..57eb0cd71 100644 --- a/Source/ARTStats.m +++ b/Source/ARTStats.m @@ -41,11 +41,7 @@ - (ARTStatsGranularity)unit { } - (void)setUnit:(ARTStatsGranularity)value { - if (self.isFrozen) { - @throw [NSException exceptionWithName:NSObjectInaccessibleException - reason:[NSString stringWithFormat:@"%@: You can't change query after you've passed it to the receiver.", self.class] - userInfo:nil]; - } + [self throwIfFrozen]; _unit = value; } diff --git a/Source/include/Ably/ARTBaseQuery.h b/Source/include/Ably/ARTBaseQuery.h index 189685c6a..eb0cb13ca 100755 --- a/Source/include/Ably/ARTBaseQuery.h +++ b/Source/include/Ably/ARTBaseQuery.h @@ -5,4 +5,6 @@ @property (nonatomic, getter=isFrozen) BOOL frozen; +- (void)throwIfFrozen; + @end From dd9a08a43abc93456fe88d0b241d9b624759395f Mon Sep 17 00:00:00 2001 From: Marat Al Date: Tue, 17 Sep 2024 14:52:07 +0200 Subject: [PATCH 3/6] Added @retroactive protocol marks for swift 6. --- Test/Test Utilities/TestUtilities.swift | 40 ++++++++++++++++++++ Test/Tests/RealtimeClientChannelsTests.swift | 9 +++++ Test/Tests/RestClientChannelsTests.swift | 8 ++++ 3 files changed, 57 insertions(+) diff --git a/Test/Test Utilities/TestUtilities.swift b/Test/Test Utilities/TestUtilities.swift index 09bb0ddac..956378ceb 100644 --- a/Test/Test Utilities/TestUtilities.swift +++ b/Test/Test Utilities/TestUtilities.swift @@ -1777,6 +1777,44 @@ extension ARTMessage { } } +#if swift(>=6) +extension ARTRealtimeConnectionState : @retroactive CustomStringConvertible { + public var description : String { + return ARTRealtimeConnectionStateToStr(self) + } +} + +extension ARTRealtimeConnectionEvent : @retroactive CustomStringConvertible { + public var description : String { + return ARTRealtimeConnectionEventToStr(self) + } +} + +extension ARTProtocolMessageAction : @retroactive CustomStringConvertible { + public var description : String { + return ARTProtocolMessageActionToStr(self) + } +} + +extension ARTRealtimeChannelState : @retroactive CustomStringConvertible { + public var description : String { + return ARTRealtimeChannelStateToStr(self) + } +} + +extension ARTChannelEvent : @retroactive CustomStringConvertible { + public var description : String { + return ARTChannelEventToStr(self) + } +} + +extension ARTPresenceAction : @retroactive CustomStringConvertible { + public var description : String { + return ARTPresenceActionToStr(self) + } +} + +#else extension ARTRealtimeConnectionState : CustomStringConvertible { public var description : String { @@ -1814,6 +1852,8 @@ extension ARTPresenceAction : CustomStringConvertible { } } +#endif + // MARK: - Custom Nimble Matchers /// A Nimble matcher that succeeds when two dates are quite the same. diff --git a/Test/Tests/RealtimeClientChannelsTests.swift b/Test/Tests/RealtimeClientChannelsTests.swift index 7cb29daa3..c3102abc8 100644 --- a/Test/Tests/RealtimeClientChannelsTests.swift +++ b/Test/Tests/RealtimeClientChannelsTests.swift @@ -2,12 +2,21 @@ import Ably import Nimble import XCTest +#if swift(>=6) +// Swift isn't yet smart enough to do this automatically when bridging Objective-C APIs +extension ARTRealtimeChannels: @retroactive Sequence { + public func makeIterator() -> NSFastEnumerationIterator { + return NSFastEnumerationIterator(iterate()) + } +} +#else // Swift isn't yet smart enough to do this automatically when bridging Objective-C APIs extension ARTRealtimeChannels: Sequence { public func makeIterator() -> NSFastEnumerationIterator { return NSFastEnumerationIterator(iterate()) } } +#endif class RealtimeClientChannelsTests: XCTestCase { // RTS2 diff --git a/Test/Tests/RestClientChannelsTests.swift b/Test/Tests/RestClientChannelsTests.swift index fb24fd945..a513c1ce7 100644 --- a/Test/Tests/RestClientChannelsTests.swift +++ b/Test/Tests/RestClientChannelsTests.swift @@ -2,12 +2,20 @@ import Ably import Nimble import XCTest +#if swift(>=6) // Swift isn't yet smart enough to do this automatically when bridging Objective-C APIs +extension ARTRestChannels: @retroactive Sequence { + public func makeIterator() -> NSFastEnumerationIterator { + return NSFastEnumerationIterator(iterate()) + } +} +#else extension ARTRestChannels: Sequence { public func makeIterator() -> NSFastEnumerationIterator { return NSFastEnumerationIterator(iterate()) } } +#endif private func beAChannel(named expectedValue: String) -> Nimble.Predicate { return Predicate.define("be a channel with name \"\(expectedValue)\"") { actualExpression, msg -> PredicateResult in From 75e95544ed4abd4c36dfa6d69af0763e60d95a0a Mon Sep 17 00:00:00 2001 From: Marat Al Date: Tue, 17 Sep 2024 15:23:36 +0200 Subject: [PATCH 4/6] Fixed Xcode 16 arosed analyser warnings. --- Source/ARTDataEncoder.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/ARTDataEncoder.m b/Source/ARTDataEncoder.m index 4169f4ac4..140b9eb69 100644 --- a/Source/ARTDataEncoder.m +++ b/Source/ARTDataEncoder.m @@ -97,6 +97,9 @@ - (ARTDataEncoderOutput *)encode:(id)data { encoded = [data dataUsingEncoding:NSUTF8StringEncoding]; encoding = [NSString artAddEncoding:@"utf-8" toString:encoding]; } + if (encoded == nil) { + return [[ARTDataEncoderOutput alloc] initWithData:data encoding:nil errorInfo:[ARTErrorInfo createWithCode:0 message:@"must be NSString, NSData, NSArray or NSDictionary."]]; + } ARTStatus *status = [_cipher encrypt:encoded output:&toBase64]; if (status.state != ARTStateOk) { ARTErrorInfo *errorInfo = status.errorInfo ? status.errorInfo : [ARTErrorInfo createWithCode:0 message:@"encrypt failed"]; @@ -181,7 +184,7 @@ - (ARTDataEncoderOutput *)decode:(id)data identifier:(NSString *)identifier enco if (status.state != ARTStateOk) { errorInfo = status.errorInfo ? status.errorInfo : [ARTErrorInfo createWithCode:ARTErrorInvalidMessageDataOrEncoding message:@"decrypt failed"]; } - } else if ([encoding isEqualToString:@"vcdiff"] && _deltaCodec) { + } else if ([encoding isEqualToString:@"vcdiff"] && _deltaCodec && [data isKindOfClass:[NSData class]]) { NSError *decodeError; data = [_deltaCodec applyDelta:data deltaId:identifier baseId:_baseId error:&decodeError]; From e20d783d0e8964d9849111a015ea1f5e3c08698c Mon Sep 17 00:00:00 2001 From: Marat Al Date: Mon, 23 Sep 2024 01:56:54 +0200 Subject: [PATCH 5/6] Added `NS_SWIFT_SENDABLE` to channel options types. --- Source/include/Ably/ARTChannelOptions.h | 1 + Source/include/Ably/ARTRealtimeChannelOptions.h | 1 + 2 files changed, 2 insertions(+) diff --git a/Source/include/Ably/ARTChannelOptions.h b/Source/include/Ably/ARTChannelOptions.h index f7028d517..a66b64d7b 100644 --- a/Source/include/Ably/ARTChannelOptions.h +++ b/Source/include/Ably/ARTChannelOptions.h @@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Passes additional properties to an `ARTRestChannel` object, such as encryption. */ +NS_SWIFT_SENDABLE @interface ARTChannelOptions : NSObject /** diff --git a/Source/include/Ably/ARTRealtimeChannelOptions.h b/Source/include/Ably/ARTRealtimeChannelOptions.h index 1bc622cd1..af43dc8b6 100644 --- a/Source/include/Ably/ARTRealtimeChannelOptions.h +++ b/Source/include/Ably/ARTRealtimeChannelOptions.h @@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Passes additional properties to an `ARTRealtimeChannel` object, such as encryption, an `ARTChannelMode` and channel parameters. */ +NS_SWIFT_SENDABLE @interface ARTRealtimeChannelOptions : ARTChannelOptions /** From 756a3830d3cd3929a8b94044ad8798192fa88ef7 Mon Sep 17 00:00:00 2001 From: Marat Al Date: Wed, 25 Sep 2024 23:31:34 +0200 Subject: [PATCH 6/6] Proper check for `retroactive` keyword. --- Test/Test Utilities/TestUtilities.swift | 6 ++---- Test/Tests/RealtimeClientChannelsTests.swift | 2 +- Test/Tests/RestClientChannelsTests.swift | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Test/Test Utilities/TestUtilities.swift b/Test/Test Utilities/TestUtilities.swift index 956378ceb..a1f7db112 100644 --- a/Test/Test Utilities/TestUtilities.swift +++ b/Test/Test Utilities/TestUtilities.swift @@ -1777,7 +1777,8 @@ extension ARTMessage { } } -#if swift(>=6) + +#if hasFeature(RetroactiveAttribute) extension ARTRealtimeConnectionState : @retroactive CustomStringConvertible { public var description : String { return ARTRealtimeConnectionStateToStr(self) @@ -1813,9 +1814,7 @@ extension ARTPresenceAction : @retroactive CustomStringConvertible { return ARTPresenceActionToStr(self) } } - #else - extension ARTRealtimeConnectionState : CustomStringConvertible { public var description : String { return ARTRealtimeConnectionStateToStr(self) @@ -1851,7 +1850,6 @@ extension ARTPresenceAction : CustomStringConvertible { return ARTPresenceActionToStr(self) } } - #endif // MARK: - Custom Nimble Matchers diff --git a/Test/Tests/RealtimeClientChannelsTests.swift b/Test/Tests/RealtimeClientChannelsTests.swift index c3102abc8..4c63cb583 100644 --- a/Test/Tests/RealtimeClientChannelsTests.swift +++ b/Test/Tests/RealtimeClientChannelsTests.swift @@ -2,7 +2,7 @@ import Ably import Nimble import XCTest -#if swift(>=6) +#if hasFeature(RetroactiveAttribute) // Swift isn't yet smart enough to do this automatically when bridging Objective-C APIs extension ARTRealtimeChannels: @retroactive Sequence { public func makeIterator() -> NSFastEnumerationIterator { diff --git a/Test/Tests/RestClientChannelsTests.swift b/Test/Tests/RestClientChannelsTests.swift index a513c1ce7..2356fed95 100644 --- a/Test/Tests/RestClientChannelsTests.swift +++ b/Test/Tests/RestClientChannelsTests.swift @@ -2,7 +2,7 @@ import Ably import Nimble import XCTest -#if swift(>=6) +#if hasFeature(RetroactiveAttribute) // Swift isn't yet smart enough to do this automatically when bridging Objective-C APIs extension ARTRestChannels: @retroactive Sequence { public func makeIterator() -> NSFastEnumerationIterator {