Skip to content
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

Add support for suppressing notifications using closure-based write/transaction methods #6252

Merged
merged 5 commits into from
Sep 23, 2019
Merged
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
31 changes: 29 additions & 2 deletions Realm/RLMRealm.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,24 @@ typedef void (^RLMNotificationBlock)(RLMNotification notification, RLMRealm *rea
/**
Performs actions contained within the given block inside a write transaction.

@see `[RLMRealm transactionWithBlock:error:]`
@see `[RLMRealm transactionWithoutNotifying:block:error:]`
*/
- (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block NS_SWIFT_UNAVAILABLE("");

/**
Performs actions contained within the given block inside a write transaction.

@see `[RLMRealm transactionWithoutNotifying:block:error:]`
*/
- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error;

/**
Performs actions contained within the given block inside a write transaction.

@see `[RLMRealm transactionWithoutNotifying:block:error:]`
*/
- (void)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block;

/**
Performs actions contained within the given block inside a write transaction.

Expand All @@ -343,14 +357,27 @@ typedef void (^RLMNotificationBlock)(RLMNotification notification, RLMRealm *rea
generates notifications if applicable. This has no effect if the Realm
was already up to date.

You can skip notifiying specific notification blocks about the changes made
in this write transaction by passing in their associated notification tokens.
This is primarily useful when the write transaction is saving changes already
made in the UI and you do not want to have the notification block attempt to
re-apply the same changes.

The tokens passed to this method must be for notifications for this specific
`RLMRealm` instance. Notifications for different threads cannot be skipped
using this method.

@param tokens An array of notification tokens which were returned from adding
callbacks which you do not want to be notified for the changes
made in this write transaction.
@param block The block containing actions to perform.
@param error If an error occurs, upon return contains an `NSError` object
that describes the problem. If you are not interested in
possible errors, pass in `NULL`.

@return Whether the transaction succeeded.
*/
- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error;
- (BOOL)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error;

/**
Updates the Realm and outstanding objects managed by the Realm to point to the
Expand Down
21 changes: 11 additions & 10 deletions Realm/RLMRealm.mm
Original file line number Diff line number Diff line change
Expand Up @@ -647,15 +647,8 @@ - (void)commitWriteTransaction {
[self commitWriteTransaction:nil];
}

- (BOOL)commitWriteTransaction:(NSError **)outError {
try {
_realm->commit_transaction();
return YES;
}
catch (...) {
RLMRealmTranslateException(outError);
return NO;
}
- (BOOL)commitWriteTransaction:(NSError **)error {
return [self commitWriteTransactionWithoutNotifying:@[] error:error];
}

- (BOOL)commitWriteTransactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens error:(NSError **)error {
Expand All @@ -681,10 +674,18 @@ - (void)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block {
}

- (BOOL)transactionWithBlock:(__attribute__((noescape)) void(^)(void))block error:(NSError **)outError {
return [self transactionWithoutNotifying:@[] block:block error:outError];
}

- (void)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block {
[self transactionWithoutNotifying:tokens block:block error:nil];
}

- (BOOL)transactionWithoutNotifying:(NSArray<RLMNotificationToken *> *)tokens block:(__attribute__((noescape)) void(^)(void))block error:(NSError **)error {
[self beginWriteTransaction];
block();
if (_realm->is_in_transaction()) {
return [self commitWriteTransaction:outError];
return [self commitWriteTransactionWithoutNotifying:tokens error:error];
}
return YES;
}
Expand Down
14 changes: 14 additions & 0 deletions Realm/Tests/NotificationTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ - (void)testSuppressRealmNotification {
[token invalidate];
}

- (void)testSuppressRealmNotificationInTransactionWithBlock {
RLMRealm *realm = [RLMRealm defaultRealm];
RLMNotificationToken *token = [realm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
XCTFail(@"should not have been called");
}];

[realm transactionWithoutNotifying:@[token] block:^{
[realm deleteAllObjects];
}];

// local realm notifications are called synchronously so no need to wait for anything
[token invalidate];
}

- (void)testSuppressRealmNotificationForWrongRealm {
RLMRealm *otherRealm = [self realmWithTestPath];
RLMNotificationToken *token = [otherRealm addNotificationBlock:^(__unused RLMNotification notification, __unused RLMRealm *realm) {
Expand Down
23 changes: 21 additions & 2 deletions RealmSwift/Realm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,35 @@ public final class Realm {
and generates notifications if applicable. This has no effect if the Realm
was already up to date.

You can skip notifiying specific notification blocks about the changes made
in this write transaction by passing in their associated notification
tokens. This is primarily useful when the write transaction is saving
changes already made in the UI and you do not want to have the notification
block attempt to re-apply the same changes.

The tokens passed to this function must be for notifications for this Realm
which were added on the same thread as the write transaction is being
performed on. Notifications for different threads cannot be skipped using
this method.

- parameter tokens: An array of notification tokens which were returned
from adding callbacks which you do not want to be
notified for the changes made in this write transaction.

- parameter block: The block containing actions to perform.

- throws: An `NSError` if the transaction could not be completed successfully.
If `block` throws, the function throws the propagated `ErrorType` instead.
*/
public func write(_ block: (() throws -> Void)) throws {
public func write(withoutNotifying tokens: [NotificationToken] = [], _ block: (() throws -> Void)) throws {
beginWrite()
do {
try block()
} catch let error {
if isInWriteTransaction { cancelWrite() }
throw error
}
if isInWriteTransaction { try commitWrite() }
if isInWriteTransaction { try commitWrite(withoutNotifying: tokens) }
}

/**
Expand Down Expand Up @@ -250,6 +265,10 @@ public final class Realm {

- warning: This method may only be called during a write transaction.

- parameter tokens: An array of notification tokens which were returned
from adding callbacks which you do not want to be
notified for the changes made in this write transaction.

- throws: An `NSError` if the transaction could not be written due to
running out of disk space or other i/o errors.
*/
Expand Down
14 changes: 14 additions & 0 deletions RealmSwift/Tests/RealmTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,20 @@ class RealmTests: TestCase {
XCTAssertEqual(try! Realm().objects(SwiftStringObject.self).count, 1)
}

func testWriteWithoutNotifying() {
let realm = try! Realm()
let token = realm.observe { _, _ in
XCTFail("should not have been called")
}

try! realm.write(withoutNotifying: [token]) {
realm.deleteAll()
}

// local realm notifications are called synchronously so no need to wait for anything
token.invalidate()
}

func testDynamicWriteSubscripting() {
try! Realm().beginWrite()
let object = try! Realm().dynamicCreate("SwiftStringObject", value: ["1"])
Expand Down