{"id":26353,"date":"2019-08-21T16:46:43","date_gmt":"2019-08-21T20:46:43","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=26353"},"modified":"2020-09-14T14:49:34","modified_gmt":"2020-09-14T18:49:34","slug":"persistent-history-tracking-in-core-data","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2019\/08\/21\/persistent-history-tracking-in-core-data\/","title":{"rendered":"Persistent History Tracking in Core Data"},"content":{"rendered":"<p><a href=\"https:\/\/stoeffn.de\/posts\/persistent-history-tracking-in-core-data\/\">Steffen Ryll<\/a>:<\/p>\n<blockquote cite=\"https:\/\/stoeffn.de\/posts\/persistent-history-tracking-in-core-data\/\">\n<p>At <em>WWDC<\/em> &rsquo;17, <em>Apple<\/em> introduced a number of new <em>Core Data<\/em> features, one of which is <em>Persistent History Tracking<\/em> or <code>NSPersistentHistory<\/code>. But as of the time of writing, its API is still undocumented. Thus, the only real reference is the <a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2017\/210\/\">What&rsquo;s New in Core Data<\/a> <em>WWDC<\/em> session.<\/p>\n<p>Since <em>Persistent History Tracking<\/em> makes sharing an <code>NSPersistentStore<\/code> across multiple processes and is one of my favorite new <em>Core Data<\/em> features, it is unfortunate that it mostly seems to fall of the radar.<\/p>\n<p>The purpose of this post is to give a real-world example on how to use it and what makes it so great.<\/p>\n<\/blockquote>\n<p>That was written a year and a half ago, and <code>NSPersistentHistory<\/code> remains a really cool feature that&rsquo;s under-discussed and <a href=\"http:\/\/www.openradar.me\/42969255\">under-documented<\/a>. Some resources I&rsquo;ve found are:<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2017\/210\/\">WWDC 2017 Session 210: What&rsquo;s New in Core Data<\/a><\/li>\n<li><a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2019\/230\/\">WWDC 2019 Session 230: Making Apps with Core Data<\/a><\/li>\n<li><a href=\"https:\/\/developer.apple.com\/documentation\/coredata\/consuming_relevant_store_changes\">Consuming Relevant Store Changes<\/a><\/li>\n<li><a href=\"https:\/\/developer.apple.com\/documentation\/coredata\/persistent_history\">Persistent History<\/a><\/li>\n<\/ul>\n<p>Here are some things I figured out by exploring:<\/p>\n<ul>\n<li>The history is stored directly in the same SQLite database(s) as the persistent store.<\/li>\n<li>It uses tables that look kind of like Core Data tables, only with a different prefix.<\/li>\n<li>But you couldn&rsquo;t create them yourself using Core Data, since the same column can store the primary key for different types of entities (you would think <code>NSObjectIDAttributeType<\/code> could do that, but it actually can&rsquo;t be used in stores), and likewise for the columns that store the tombstone values.<\/li>\n<li>The tables are updated using SQLite triggers, which are again not directly exposed in Core Data (though this year&rsquo;s new derived attributes also use them).<\/li>\n<li>The triggers are fired for all database changes, so unlike the managed object context&rsquo;s change tracking, they also work for batch updates and deletions (and, presumably, the forthcoming batch insertions).<\/li>\n<li>But they don&rsquo;t tombstone attributes that use <code>allowsExternalBinaryDataStorage<\/code>, even for small values that are stored in the database. In my opinion, Core Data should report an error if you try to use a model that&rsquo;s configured in this way.<\/li>\n<li>The tables look very compact, with repeated string values interned and the list of modified columns stored as a bit vector.<\/li>\n<li>Core Data automatically updates the schema of the history tracking tables when you do a migration.<\/li>\n<li>Enabling history tracking does not change the version of your model. But, in practice, you&rsquo;ll get incorrect results if you don&rsquo;t enable it consistently.<\/li>\n<li>Setting an attribute to be preserved after deletion (i.e. for the tombstone) does change the model&rsquo;s version hash, however.<\/li>\n<li>There&rsquo;s no public API to set this flag on an attribute in the model, only a checkbox in Xcode. However, you can use key-value coding to set or query <code>NSPropertyDescription.preserveValueOnDeletionInPersistentHistory<\/code>.<\/li>\n<li>So, overall, it seems tricky to use persistent history on a store that will be shared with OS versions that don&rsquo;t support history tracking. You might have to roll your own in that case.<\/li>\n<li>Querying and pruning the history works as you would expect.<\/li>\n<li>The <code>NSPersistentHistoryTransaction.objectIDNotification()<\/code> does not generate a <code>NSManagedObjectContextDidSaveNotification<\/code>, but rather a private <code>NSManagedObjectContextDidSaveObjectIDsNotification<\/code> notification.<\/li>\n<li>Rather than containing full objects under keys like <code>NSUpdatedObjectsKey<\/code>, it contains object IDs under keys like <code>updated_objectIDs<\/code>. This is a bit unexpected, because <code>NSManagedObjectContext<\/code> is already documented to support <code>NSManagedObjectID<\/code> or <code>NSURL<\/code> objects under the <code>NSUpdatedObjectsKey<\/code> key.<\/li>\n<li>In any case, you get IDs because it isn&rsquo;t storing the changed values. Instead, when merging, it fetches the latest values from the store.<\/li>\n<li>This makes sense given the data model, but it means that, perhaps counterintuitively, merging will update <em>all<\/em> the attributes, not just those those changed in the transaction that generated the notification. And they&rsquo;ll be updated to the <em>current<\/em> values, which may be much newer than the ones at the time of the transaction. This is not version control, just a way to see what has changed.<\/li>\n<\/ul>\n\n<p id=\"persistent-history-tracking-in-core-data-update-2019-08-22\">Update (2019-08-22): <a href=\"https:\/\/twitter.com\/deeje\/status\/1164281454689304576\">Deeje Cooley<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/deeje\/status\/1164281454689304576\">\n<p>I incorporated Persistent History Tracking into <a href=\"https:\/\/github.com\/deeje\/CloudCore\">CloudCore<\/a>, an open-source CoreData-CloudKit sync engine, specifically to support offline sync.  Check it out!<\/p>\n<\/blockquote>\n\n<p id=\"persistent-history-tracking-in-core-data-update-2020-09-14\">Update (2020-09-14): See also: <a href=\"https:\/\/www.avanderlee.com\/swift\/persistent-history-tracking-core-data\/\">Antoine van der Lee<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Steffen Ryll: At WWDC &rsquo;17, Apple introduced a number of new Core Data features, one of which is Persistent History Tracking or NSPersistentHistory. But as of the time of writing, its API is still undocumented. Thus, the only real reference is the What&rsquo;s New in Core Data WWDC session. Since Persistent History Tracking makes sharing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2019-08-21T20:46:47Z","apple_news_api_id":"ada98fbb-9e2e-4a34-9a9a-6c1dc4ce3d9f","apple_news_api_modified_at":"2020-09-14T18:49:41Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAg==","apple_news_api_share_url":"https:\/\/apple.news\/AramPu54uSjSammwdxM49nw","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[109,164,31,1472,1610,1245,30,1529,1609,71,425],"class_list":["post-26353","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-coredata","tag-documentation","tag-ios","tag-ios-11","tag-ios-12","tag-key-value-coding-kvc","tag-mac","tag-macos-10-13","tag-macos-10-14","tag-programming","tag-sqlite"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/26353","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=26353"}],"version-history":[{"count":4,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/26353\/revisions"}],"predecessor-version":[{"id":30113,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/26353\/revisions\/30113"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=26353"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=26353"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=26353"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}