From 535097ab963710cf4137f858e262f198521ff72f Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 14 Dec 2023 14:13:28 -0800 Subject: [PATCH] Allow creating notifiers inside write transactions before the first change Core has allowed this for a while, but we had our own validation which made it not work. --- CHANGELOG.md | 20 ++++++++++++++++++++ Realm/RLMCollection.mm | 7 ++++++- Realm/RLMObjectBase.mm | 9 +++++++-- Realm/RLMRealm.mm | 3 --- Realm/Tests/AsyncTests.mm | 14 +++++++++----- RealmSwift/Tests/ObjectTests.swift | 2 -- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d81a82d72..daedb02600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +x.y.z Release notes (yyyy-MM-dd) +============================================================= +### Enhancements +* None. + +### Fixed +* Registering new notifications inside write transactions before actually + making any changes is now actually allowed. This was supposed to be allowed + in 10.39.1, but it did not actually work due to some redundant validation. + +### Compatibility +* Realm Studio: 14.0.1 or later. +* APIs are backwards compatible with all previous releases in the 10.x.y series. +* Carthage release for Swift is built with Xcode 15.1.0. +* CocoaPods: 1.10 or later. +* Xcode: 14.1-15.1.0. + +### Internal +* Upgraded realm-core from ? to ? + 10.45.1 Release notes (2023-12-18) ============================================================= diff --git a/Realm/RLMCollection.mm b/Realm/RLMCollection.mm index aebaf1eb63..6826e302db 100644 --- a/Realm/RLMCollection.mm +++ b/Realm/RLMCollection.mm @@ -506,7 +506,12 @@ - (bool)invalidate { RLMClassInfo *info = collection.objectInfo; if (!queue) { [realm verifyNotificationsAreSupported:true]; - token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)]; + try { + token->_token = [collection addNotificationCallback:block keyPaths:info->keyPathArrayFromStringArray(keyPaths)]; + } + catch (const realm::Exception& e) { + @throw RLMException(e); + } return token; } diff --git a/Realm/RLMObjectBase.mm b/Realm/RLMObjectBase.mm index 8dbf6f48b9..a31310fe43 100644 --- a/Realm/RLMObjectBase.mm +++ b/Realm/RLMObjectBase.mm @@ -752,8 +752,13 @@ - (void)observe:(RLMObjectBase *)obj completion(); } }; - _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj, completion}, - obj->_info->keyPathArrayFromStringArray(keyPaths)); + try { + _token = _object.add_notification_callback(ObjectChangeCallbackWrapper{block, obj, completion}, + obj->_info->keyPathArrayFromStringArray(keyPaths)); + } + catch (const realm::Exception& e) { + @throw RLMException(e); + } } - (void)registrationComplete:(void (^)())completion { diff --git a/Realm/RLMRealm.mm b/Realm/RLMRealm.mm index 9109d0e0ba..268a65ad6f 100644 --- a/Realm/RLMRealm.mm +++ b/Realm/RLMRealm.mm @@ -550,9 +550,6 @@ - (void)verifyNotificationsAreSupported:(bool)isCollection { if (_realm->config().automatic_change_notifications && !_realm->can_deliver_notifications()) { @throw RLMException(@"Can only add notification blocks from within runloops."); } - if (isCollection && _realm->is_in_transaction()) { - @throw RLMException(@"Cannot register notification blocks from within write transactions."); - } } - (RLMNotificationToken *)addNotificationBlock:(RLMNotificationBlock)block { diff --git a/Realm/Tests/AsyncTests.mm b/Realm/Tests/AsyncTests.mm index 819c46b45b..854297da09 100644 --- a/Realm/Tests/AsyncTests.mm +++ b/Realm/Tests/AsyncTests.mm @@ -859,11 +859,15 @@ - (void)testAsyncNotSupportedForReadOnlyRealms { }]); } -- (void)testAsyncNotSupportedInWriteTransactions { - [RLMRealm.defaultRealm transactionWithBlock:^{ - XCTAssertThrows([IntObject.allObjects addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) { - XCTFail(@"should not be called"); - }]); +- (void)testAsyncNotSupportedAfterMakingChangesInWriteTransactions { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm transactionWithBlock:^{ + XCTAssertNoThrow([IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {}]); + [IntObject createInRealm:realm withValue:@[@0]]; + RLMAssertThrowsWithReason([IntObject.allObjects addNotificationBlock:^(RLMResults *, RLMCollectionChange *, NSError *) {}], + @"Cannot create asynchronous query after making changes in a write transaction."); + RLMAssertThrowsWithReason([IntObject.allObjects[0] addNotificationBlock:^(BOOL, NSArray *, NSError *) {}], + @"Cannot create asynchronous query after making changes in a write transaction."); }]; } diff --git a/RealmSwift/Tests/ObjectTests.swift b/RealmSwift/Tests/ObjectTests.swift index 06918f6964..a29697d519 100644 --- a/RealmSwift/Tests/ObjectTests.swift +++ b/RealmSwift/Tests/ObjectTests.swift @@ -1580,7 +1580,6 @@ class ObjectTests: TestCase { // This test consistently crashes inside the Swift runtime when building // with SPM. - #if !SWIFT_PACKAGE @MainActor @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testAsyncSequenceObserve() async throws { @@ -1608,7 +1607,6 @@ class ObjectTests: TestCase { task.cancel() _ = try await task.value } - #endif @MainActor @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)