<!doctypehtml><html lang=en><link href=https://gmpg.org/xfn/11 rel=profile><meta charset=utf-8><meta content=IE=edge http-equiv=X-UA-Compatible><meta content=public http-equiv=Cache-Control><meta content=width=device-width,initial-scale=1.0,maximum-scale=1 name=viewport><meta content="Hugo 0.64.1"name=generator><title>
   Sharing data between iOS apps and app extensions • The Atomic Birdhouse
  </title><meta content=summary name=twitter:card><meta content="Sharing data between iOS apps and app extensions"name=twitter:title><meta content="Since iOS app extensions run as part of a host application rather than as part of their containing app (i.e. your app’s extensions run in somebody else’s app), data sharing isn’t automatic. Finding standard locations like the documents directory doesn’t work for shared data. In this post I’ll go through the details of how to make it all work.
Along the way I’ll get into how to set up real-time messaging between apps and their extensions."name=twitter:description><meta content="Sharing data between iOS apps and app extensions"property=og:title><meta content="Since iOS app extensions run as part of a host application rather than as part of their containing app (i.e. your app’s extensions run in somebody else’s app), data sharing isn’t automatic. Finding standard locations like the documents directory doesn’t work for shared data. In this post I’ll go through the details of how to make it all work.
Along the way I’ll get into how to set up real-time messaging between apps and their extensions."property=og:description><meta content=article property=og:type><meta content=https://atomicbird.com/blog/sharing-with-app-extensions/ property=og:url><meta content=2014-11-20T00:00:00+00:00 property=article:published_time><meta content=2014-11-20T00:00:00+00:00 property=article:modified_time><meta content=Title property=og:site_name><link href=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css rel=stylesheet><link href=/scss/hyde-hyde.db44dfb5bd5435c4a9e24d126b92af9810236400739f64c43ba04459083e062c.css integrity=sha256-20Tftb1UNcSp4k0Sa5KvmBAjZABzn2TEO6BEWQg+Biw= rel=stylesheet><link href=/scss/print.2744dcbf8a0b2e74f8a50e4b34e5f441be7cf93cc7de27029121c6a09f9e77bc.css integrity=sha256-J0Tcv4oLLnT4pQ5LNOX0Qb58+TzH3icCkSHGoJ+ed7w= media=print rel=stylesheet><link href=/apple-touch-icon-144-precomposed.png rel=apple-touch-icon-precomposed sizes=144x144><link rel="shortcut icon"href=/favicon.png><link title="Atomic Birdhouse"href=https://www.atomicbird.com/index.xml rel=alternate type=application/rss+xml><body class=theme-base-07><div class=sidebar><div class=container><div class=sidebar-about><span class=site__title> <a href=https://atomicbird.com/> The Atomic Birdhouse </a> </span><div class=author-image><img alt="Author Image"class="img--circle img--headshot element--center"src=https://atomicbird.com/images/tom-2019-256.jpeg></div><p class=site__description>One software developer's search for meaning</div><div class=collapsible-menu><input id=menuToggle type=checkbox><label for=menuToggle>The Atomic Birdhouse</label><div class=menu-content><div><ul class=sidebar-nav><li><a href=/blog/> <span> Posts </span> </a><li><a href=/about/> <span> About </span> </a><li><a href=/contact/> <span> Contact </span> </a><li><a href=/index.xml> <span> RSS </span> </a></ul></div><section class=social><a href=https://twitter.com/atomicbird rel=me> <i class="fab fa-twitter fa-lg"aria-hidden=true> </i> </a><a href=https://facebook.com/atomicbird rel=me> <i class="fab fa-facebook-f"> </i> </a><a href=https://github.com/atomicbird rel=me> <i class="fab fa-github fa-lg"aria-hidden=true> </i> </a><a href=https://instagram.com/atomicbird rel=me> <i class="fab fa-instagram fa-lg"aria-hidden=true> </i> </a><a href=https://linkedin.com/in/tomharrington rel=me> <i class="fab fa-linkedin fa-lg"aria-hidden=true> </i> </a><a href=https://stackoverflow.com/users/43832 rel=me> <i class="fab fa-stack-overflow fa-lg"aria-hidden=true> </i> </a><a href=https://keybase.io/atomicbird rel=me> <i class="fab fa-keybase fa-lg"aria-hidden=true> </i> </a></section></div></div><div class=copyright>© 2020 Tom Harrington <a href=https://creativecommons.org/licenses/by-sa/4.0> CC BY-SA 4.0 </a></div></div></div><div class="content container"><article><header><h1>Sharing data between iOS apps and app extensions</h1><div class=post__meta><i class="fas fa-calendar-alt"> </i> Nov 20, 2014 <br><i class="fas fa-tags"> </i><a class="badge badge-tag"href=/tags/ios> ios </a><a class="badge badge-tag"href=/tags/app-extensions> app extensions </a><a class="badge badge-tag"href=/tags/watchkit> watchkit </a><br><i class="fas fa-clock"> </i> 9 min read</div></header><div class=post><p>Since iOS app extensions run as part of a host application rather than as part of their containing app (i.e. your app’s extensions run in somebody else’s app), data sharing isn’t automatic. Finding standard locations like the documents directory doesn’t work for shared data. In this post I’ll go through the details of how to make it all work.<p>Along the way I’ll get into how to set up real-time messaging between apps and their extensions. Not Cocoa notifications, but a variation of file-based <a href=http://en.m.wikipedia.org/wiki/Interprocess_communication> IPC </a> that includes a notification system.<p>Most of this is not actually specific to iOS extensions, though it’s probably more useful with extensions than in other situations.<p>Code snippets are included, and see <a href=https://github.com/atomicbird/iOS-Extension-Demo> GitHub </a> for the full project.<h2 id=sharing-non-local-or-non-app-data>Sharing non-local or non-app data</h2><p>Of course the easy way is to just not bother sharing local app-specific data at all. If the data is either non-local or not specific to your app, sharing may already be covered.<p>Non-local data implies that it’s linked to or available from some out-of-app source, like a web server somewhere. Since app extensions often don’t run for very long, adding network latency might not be viable. But in principle there’s no reason an extension can’t make the same network calls as the app.<p>Apple’s approach to this in their <a href=https://developer.apple.com/library/prerelease/ios/samplecode/Lister/Introduction/Intro.html> Lister </a> demo app is to use iCloud with Core Data. That’s also non-local since it syncs to the iCloud service, but has the benefit of system-level local caching to avoid network delays. Of course, Core Data with iCloud has its own set of issues…<p>Data that’s not specific to your app would be something like the iOS address book database. If you’re using data where Apple already gives you an API for shared data, you’re set.<p> <h2 id=set-up-an-app-group>Set up an App Group</h2><p>App Groups are the scheme iOS uses to allow different apps to share data. If the apps have the right entitlements and proper provisioning, they can access a shared directory outside of their normal iOS sandbox. Sandboxing still applies except for a single exception.<p>This is <em> probably </em> almost automatic, but when you get into iOS app provisioning you can’t assume anything. What’s supposed to happen is that you just turn on the “app groups” entitlement in Xcode for the app and for any extensions.<p><img alt src=app-groups.png#center><p>When you flip that switch, Xcode will talk to the developer center to configure your app ID for app groups. Next it’ll ask you for a group name. Give it one and it’ll create and download a new provisioning profile.<p>If that doesn’t work (and let’s face it, with provisioning it’s a crapshoot) you can keep trying or else log in to the dev center and do it by hand. It’s less convenient but hardly impossible.<h2 id=using-your-app-group>Using your App Group</h2><p>The simplest way to use the app group is for shared user defaults. It’s extremely easy. Instead of using the ubiquitous <code>
       [NSUserDefaults standardUserDefaults]
      </code> call, create a custom user defaults object:<div class=highlight><pre style=background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-objectivec data-lang=objectivec>NSUserDefaults *myDefaults = [[NSUserDefaults alloc]
    initWithSuiteName:<span style=color:#009c00>@"</span><span style=color:#009c00>group.com.atomicbird.demonotes</span><span style=color:#009c00>"</span>];
[myDefaults setObject:<span style=color:#009c00>@"</span><span style=color:#009c00>foo</span><span style=color:#009c00>"</span> forKey:<span style=color:#009c00>@"</span><span style=color:#009c00>bar</span><span style=color:#009c00>"</span>];
</code></pre></div><p>That’s what Apple describes in the <a href=https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/index.html#//apple_ref/doc/uid/TP40014214-CH20-SW1> App Extension Programming Guide </a> , and it’s fantastic if you don’t need to share very much data and you don’t need notifications of changes.<p>If you need to share more data than really works for user defaults, you can access the shared group directory directly via <code>
       NSFileManager
      </code> :<div class=highlight><pre style=background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-objectivec data-lang=objectivec>NSURL *groupURL = [[NSFileManager defaultManager]
    containerURLForSecurityApplicationGroupIdentifier:
        <span style=color:#009c00>@"</span><span style=color:#009c00>group.com.atomicbird.demonotes</span><span style=color:#009c00>"</span>];
</code></pre></div><p>Any app or extension with matching group entitlements can access the same directory, so any data saved there is shared among all of them. If you want any sub-directories, you’ll need to create them.<p> <h2 id=keep-your-data-intact>Keep Your Data Intact</h2><p><strong> But first </strong> , make sure that you don’t accidentally corrupt the data. Sharing data files means there might be more than one process trying to use a file at the same time. Sandboxing on iOS means this is a somewhat rare situation, but that doesn’t mean it’s OK to ignore it.<p><strong> Update </strong> , 2015-05-18: The original version of this post recommended using <code>
       NSFileCoordinator
      </code> and <code>
       NSFilePresenter
      </code> . I later (2014-11-29) removed that because Apple’s <a href=https://developer.apple.com/library/ios/technotes/tn2408/_index.html> Tech Note 2408 </a> , warned against doing so, noting that<blockquote><p>When you create a shared container for use by an app extension and its containing app in iOS 8, you are obliged to write to that container in a coordinated manner to avoid data corruption. However, you must not use file coordination APIs directly for this.</blockquote><p>However last week this was updated to read:<blockquote><p>When you create a shared container for use by an app extension and its containing app in iOS 8.0 or later, you are obliged to write to that container in a coordinated manner to avoid data corruption. However, you must not use file coordination APIs directly for this <strong> in iOS 8.1.x and earlier </strong> . [emphasis mine]</blockquote><p>It also notes that problems with file coordination have been resolved in iOS 8.2. As a result I’m restoring the original discussion of data sharing. <strong> But note that this only applies with iOS 8.2 and up. </strong> Thanks to <a href=https://twitter.com/phatblat/status/599692803728232448> Ben Chatelain </a> for pointing out this latest change. Also thanks to <a href=https://twitter.com/lazerwalker> @lazerwalker </a> and <a href=https://twitter.com/arix> Ari Weinstein </a> for pointing out the original problems.<p>You’ll want to use <code>
       NSFileCoordinator
      </code> any time you want to read or write your shared files. You’ll also want to implement <code>
       NSFilePresenter
      </code> any time you need to know if a file has changed. These were introduced as companions to iCloud, where both your app and the iCloud daemon might want to access the same file. They’re not iCloud specific, though.<h5 id=nsfilecoordinator>NSFileCoordinator</h5><p><code>
       NSFileCoordinator
      </code> implements a <a href=http://en.m.wikipedia.org/wiki/Readers%E2%80%93writer_lock> read/write lock </a> for file access that can coordinate access between different processes. It helps ensure that a process gets exclusive access to a file when writing to it.<p>Doing a coordinated read looks something like this, in my demo app:<div class=highlight><pre style=background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-objectivec data-lang=objectivec>NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] 
    initWithFilePresenter:self];
NSError *fileCoordinatorError = nil;
<span style=color:#00f>__block</span> NSArray *savedNotes = nil;

[fileCoordinator coordinateReadingItemAtURL:[self demoNoteFileURL] options:0 
    error:&fileCoordinatorError byAccessor:^(NSURL *newURL) {
    
    NSData *savedData = [NSData dataWithContentsOfURL:newURL];
    
    <span style=color:#00f>if</span> (savedData != nil) {
        savedNotes = [NSKeyedUnarchiver unarchiveObjectWithData:savedData];
    }
}];
</code></pre></div><p>The block is where the actual reading takes place, and the rest of the code ensures that the reading doesn’t happen while someone else is changing the file. The <code>
       self
      </code> argument to <code>
       initWithFilePresenter:
      </code> isn’t mandatory, but if you use <code>
       NSFilePresenter
      </code> you should include a presenting object.<p>A coordinated write follows the same pattern, though the locking happens differently. There are other useful methods on <code>
       NSFileCoordinator
      </code> for cases like reading a file, making a change, and then writing the new version.<p>It’s important to be aware that <code>
       NSFileCoordinator
      </code> methods run <strong> synchronously </strong> , so your code will block until they complete. That’s convenient since you don’t have to wait for an asynchronous block callback. But it also means that they block the current thread. If some other process is going to be busy with the file for a long time, you’ll end up waiting on it.<h5 id=nsfilepresenter>NSFilePresenter</h5><p><code>
       NSFilePresenter
      </code> is a protocol that you can add to any class. A file <em> presenter </em> is just any object that cares about the state of the file and wants to know when things happen to it. Most of the methods are optional and are there to notify you that the file has changed in one way or another so that your code can respond. Some other methods advise your code of things it probably should do– for example “hey, now would be a good time to save any changes you have” ( <code>
       savePresentedItemChangesWithCompletionHandler:
      </code> ).<p>Which file presenter methods you implement depends on how much you need to know about changes to your shared files. The simplest case is probably to use <code>
       presentedItemDidChange
      </code> but no others. That’s a generic call that tells you that some other process (your app or your extension) changed the contents of the file. What you do depends on how you use the data.<p>Make sure to pass the file presenter object to the <code>
       NSFileCoordinator
      </code> when you create it. Although it’s not strictly necessary, it helps prevent your code being notified of its own changes. Also, if you’re implementing methods like <code>
       presentedItemDidChange:
      </code> , make sure to tell <code>
       NSFileCoordinator
      </code> that you’re interested:<div class=highlight><pre style=background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-objectivec data-lang=objectivec>[NSFileCoordinator addFilePresenter:self];
</code></pre></div><p>Then you should be all set to be notified of changes to the file.<h5 id=other-options>Other options</h5><p>If you’re not using custom code to read and write your data, you can skip file coordination and instead go with atomic read/write operations. For example, <code>
       writeToFile:atomically:
      </code> on property list classes like <code>
       NSArray
      </code> should be safe without doing your own file coordination. Likewise any data saved with <code>
       NSUserDefaults
      </code> should avoid corruption without extra coordination code. Also, SQLite (and by extension, Core Data) is smart enough to handle having multiple processes access its data.<p>But this just handles keeping the data intact. You’ll still want to make sure that your app and its extensions are aware of new changes so they can present current data to the user. For that you’ll need some kind of notification system.<p> <h2 id=notifications-between-apps-and-app-extensions>Notifications between Apps and App Extensions</h2><p>There’s still no full IPC mechanism on iOS. <code>
       NSDistributedNotificationCenter
      </code> hasn’t made the jump from OS X to iOS and probably never will. But file coordination and presentation can serve the same purpose, as long as the apps use the same app group.<p><strong> Update </strong> , 2015-05-18: As with the discussion above, this section originally used <code>
       NSFilePresenter
      </code> , but that was removed, and is now being restored.<p>When I was adding file coordination and presentation to my demo app, I realized that they could also be used for notifications between an app and its extensions. If one of them does a coordinated write while the other is using a file presenter for the file, the call to <code>
       presentedItemDidChange
      </code> happens almost instantly. Notification is the whole purpose of that method, so it makes sense it would work this way. I want to be notified if a specific file changes, and that’s how I get the notification.<p>But you don’t need to care about the file contents to be interested in notifications. If you just want a notification, choose a file name and use it as the notification mechanism. Any time one process needs to notify the other, make a change to the file. The other will get a file presenter call, and the notification is complete. It feels sort of like a hack but really this is exactly how the API is designed to work.<p>Another approach that has become very popular is the <a href=https://github.com/mutualmobile/MMWormhole> MMWormhole </a> project. It bypasses file presenters and uses Darwin-level notifications via <code>
       CFNotificationCenterGetDarwinNotifyCenter
      </code> . This actually is a little bit like <code>
       NSDistributedNotificationCenter
      </code> . Either approach works.<h5 id=for-watch-apps-only>For Watch Apps Only</h5><p>If you’re writing an Apple Watch app, you have one more option not available to other types of app extension. In your <code>
       WKInterfaceController
      </code> subclass, call <code>
       openParentApplication:reply:
      </code> to pass data to the containing app and get a response. That will trigger a call to <code>
       application:handleWatchKitExtensionRequest:reply:
      </code> in the containing app’s app delegate. This method serves as a live notification but can also carry arbitrary data.<p>Unlike other approaches, this has the benefit that it will launch the containing app if it’s not already running. Using file coordination or <code>
       MMWormhole
      </code> is great but they can’t launch the containing app.<p>The drawback to this approach is that it can only be initiated from the Watch app. The containing app doesn’t have a corresponding call to tell the Watch app that new data is available.<p>This scheme only exists for Watch apps, not for other types of app extension. I’m hopeful that Apple will add something comparable (rdar://19207935). For now, other extension types will need to use other communication approaches.</div><div class="navigation navigation-single"><a class=navigation-prev href=/blog/ios-extension-demo/> <i class="fa fa-chevron-left"aria-hidden=true> </i> <span class=navigation-tittle> iOS 8 App Extension Demo </span> </a><a class=navigation-next href=/blog/swift-core-data/> <span class=navigation-tittle> Using Core Data with Swift </span> <i class="fa fa-chevron-right"aria-hidden=true> </i> </a></div></article></div><script>var doNotTrack = false;
if (!doNotTrack) {
	window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
	ga('create', 'UA-413450-1', 'auto');
	
	ga('send', 'pageview');
}</script><script async src=https://www.google-analytics.com/analytics.js></script><script crossorigin defer integrity=sha384-GqVMZRt5Gn7tB9D9q7ONtcp4gtHIUEW/yG7h98J7IpE3kpi+srfFyyB/04OV6pG0 src=https://use.fontawesome.com/releases/v5.5.0/js/all.js></script><script src=https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js></script><script>hljs.initHighlightingOnLoad();</script>