TN3149: Fetching Contacts change history events
Learn how to fetch and process the most recent changes to the Contacts database.
Overview
The Contacts framework has a new fetch request to retrieve information from the user’s Contacts database. A change history fetch request efficiently returns a collection of change history events that describes the contacts and groups added to, deleted from, and updated in the Contacts database. You can fetch all changes in the database or limit the search to changes that have occurred since your last fetch operation. Then, inspect the fetch result to determine what changes have occurred such as a contact was updated or a contact was removed from a group. To fetch change history events in your app, follow these steps:
Adopt the change history event visitor protocol.
Create a change history fetch request.
Configure the change history fetch request
Execute the change history fetch request.
Process a change history event.
Save the starting history token.
Adopt the change history event visitor protocol
The CNChangeHistoryEvent class encapsulates all possible changes that the system can make to the Contacts database, such as dropping cached information, adding a contact, or updating a group. To receive these changes in your app, create and use a class that conforms to the CNChangeHistoryEventVisitor protocol. Classes adopting this protocol must implement the visitDropEverythingEvent:, visitAddContactEvent:, visitUpdateContactEvent:, and visitDeleteContactEvent: instance methods.
@interface MyEventVisitor : NSObject <CNChangeHistoryEventVisitor>
@end
@implementation MyEventVisitor
// Drop all cached information.
- (void)visitDropEverythingEvent:(CNChangeHistoryDropEverythingEvent *)event {
NSLog(@"Received a drop everything event.");
}
// The user added a contact to the database.
- (void)visitAddContactEvent:(CNChangeHistoryAddContactEvent *)event {
NSLog(@"The user added a contact with family name, %@, to their account with identifier %@.",event.contact.familyName, event.containerIdentifier);
}
// The user updated a contact in the database.
- (void)visitUpdateContactEvent:(CNChangeHistoryUpdateContactEvent *)event {
NSLog(@"The user updated the contact with family name, %@.",event.contact.familyName);
}
// The user removed a contact from the database.
- (void)visitDeleteContactEvent:(CNChangeHistoryDeleteContactEvent *)event {
NSLog(@"The user removed the contact identified by %@ from the database.",event.contactIdentifier);
}
@endCNChangeHistoryEventVisitor also provides optional methods such as visitAddMemberToGroupEvent:. Each method provides the change history event associated with the changes. Inspect the properties of the event to detemine the new values or modifications brought to contact properties. For example, visitUpdateContactEvent: provides a CNChangeHistoryUpdateContactEvent object whose contact instance property contains updated information about an existing contact. Compare contact to your app’s cached contact properties to determine what properties were updated.
Create a change history fetch request
Call CNChangeHistoryFetchRequest to create a change history fetch request.
CNChangeHistoryFetchRequest *fetchRequest = [[CNChangeHistoryFetchRequest alloc] init];The change history fetch request can be configured to fetch a range of change events that have occurred to the Contacts database. The range of events begins with a starting history token, startingToken, and ends with the current state of the database. This starting token has a default value of nil. If you provide an existing starting token from a previous change history fetch, the request only returns changes that occurred in history after this token.
@property (nonatomic, copy) NSData *historyToken;
self.historyToken = nil;
fetchRequest.startingToken = self.historyToken;If your token has a nil value, is invalid or expired, the fetch request returns a drop event followed by an add event for every contact and group in the Contacts database. The drop event indicates that apps should drop cached information. The token can be persisted between the app launches on the current device.
Configure a change history fetch request
Each CNChangeHistoryFetchRequest object comes with default values that can affect the result of the operation. CNChangeHistoryFetchRequest provides several properties to update these default values.
The fetch request always returns changes to contacts. Changes to groups are optional. If you want to also fetch group changes, set includeGroupChanges to YES. This property indicates whether to add group changes to the fetch result. The default value is NO.
fetchRequest.includeGroupChanges = YES;The fetch request returns immutable CNContact and CNGroup objects. To fetch mutable contact and group objects, set mutableObjects to YES. This property indicates whether to return mutable objects. The default value is NO.
fetchRequest.mutableObjects = YES;The fetch request returns contact changes as unified contacts. A unified contact is a synthesized merged result of contacts from different accounts that represent the same person. To fetch individual contact changes, set shouldUnifyResults to NO. This property specifies whether to return contact changes for either unified contacts or individual contacts. The default value is YES.
fetchRequest.shouldUnifyResults = NO;The Contacts framework limits the contact properties that you fetch to the unique identifier when executing a change history fetch request. The request always and primarily returns the CNContactIdentifierKey key. To fetch additional contact properties, update additionalContactKeyDescriptors with contact keys. This property takes an array of key descriptors, CNKeyDescriptor. For example, if you want to fetch the email addresses and family name of contacts in addition to their unique identifiers, add the CNContactEmailAddressesKey and CNContactEmailAddressesKey keys to additionalContactKeyDescriptors .
fetchRequest.additionalContactKeyDescriptors = @[CNContactEmailAddressesKey, CNContactFamilyNameKey];A transaction author, transactionAuthor, is a string that you provide to identify the author who performs a save request. Transaction authors use reverse-domain-style notation. We recommend using your app’s bundle identifier as the transaction author when your app is saving changes to Contacts.
CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
saveRequest.transactionAuthor = @"com.mycompany.myappname";You can later filter that author from fetched change history events. The fetch result won’t include changes made by authors you would like to ignore or exclude. To exclude changes made by certain authors, update excludedTransactionAuthors with these authors.
fetchRequest.excludedTransactionAuthors = @[@"com.mycompany.myappname"];Execute the change history fetch request
Call enumeratorForChangeHistoryFetchRequest:error: on an instance of CNContactStore to execute the change history fetch request. Pass the created CNChangeHistoryFetchRequest object and a NSError object to the call.
NSError *error = nil;
// This method is unavailable in Swift.
CNFetchResult<NSEnumerator<CNChangeHistoryEvent *> *> *fetchResult = [self.store enumeratorForChangeHistoryFetchRequest:fetchRequest error:&error];If the fetch request succeeds, enumeratorForChangeHistoryFetchRequest:error: returns a CNFetchResult object. Process the change history events returned in the value property of CNFetchResult. If the fetch request fails, enumeratorForChangeHistoryFetchRequest:error: returns nil.
Process a change history event
To process a change history event, call acceptEventVisitor: on an instance of a class that conforms to CNChangeHistoryEventVisitor.
MyEventVisitor *myEventVisitor = [[MyEventVisitor alloc] init];
for (CNChangeHistoryEvent *event in fetchResult.value) {
[event acceptVisitor:myEventVisitor];
}If your app receives a drop everything event, enough has changed since the last time your app fetched the history changes that an incremental sync is no longer possible. Following the drop everything event, your app receives an add event for each contact and group currently in the database. This allows you to implement full syncs and incremental syncs using the same code.
Save the starting history token
If the fetch request succeeds, inspect the currentHistoryToken property of CNFetchResult. This property provides the history token for the current fetch request. Your app should save this token, then use it when fetching the next change history events.
self.historyToken = fetchResult.currentHistoryToken; Revision History
2023-06-06 First published.
See Also
Latest
TN3205: Low-latency communication with RDMA over ThunderboltTN3206: Updating Apple Pay certificatesTN3179: Understanding local network privacyTN3190: USB audio device design considerationsTN3194: Handling account deletions and revoking tokens for Sign in with AppleTN3193: Managing the on-device foundation model’s context windowTN3115: Bluetooth State Restoration app relaunch rulesTN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen keyTN3151: Choosing the right networking APITN3111: iOS Wi-Fi API overviewTN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOSTN3134: Network Extension provider deploymentTN3189: Managing Mail background traffic loadTN3187: Migrating to the UIKit scene-based life cycleTN3188: Troubleshooting In-App Purchases availability in the App Store