31

TLDR: Is it possible to send realtime messages or notifications between iOS App and it's Extension?

I'm writing an iOS App with an extension that are part of the same App Group and share the same CoreData (SQLite database). I can read and write to the database using CoreData from the App and from the extension, they both share the same content.

My Question is: Is it possible to send messages or notifications between the App and the extension to notify the other to update if necessary?

I tried sending notifications through NSNotificationCenter but that does not go "out" of the App/Extension, same issue if I try to write to the group shared NSUserDefaults and listen to NSUserDefaultsDidChangeNotification. This works inside the App but the extension does not receive anything (when I know that it is launched and it share the same NSUserDefaults). Any idea how to keep things in sync?

Ludovic Landry
  • 11,606
  • 10
  • 48
  • 80
  • Also see docs: [Sharing Data with Your Containing App](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#:~:text=To%20enable%20data%20sharing%2C%20use,App%20to%20an%20App%20Group.) – mfaani Jun 11 '20 at 13:24

4 Answers4

38

TLDR: No, but there's a hack

There's no true interprocess communication for iOS apps, with or without extensions. NSDistributedNotification still hasn't made the trip from OS X to iOS, and probably won't.

With some extension types you can open URLs via NSExtensionContext and use them to pass data to an app that handles the URL. This brings the app to the foreground, which doesn't sound like what you want.

There is a hack that might get you what you need, though.

  • Instead of writing to user defaults, write to a file in your app group directory.
  • Don't just write the file directly-- use NSFileCoordinator to do coordinated writes to the file.
  • Implement NSFilePresenter on an object that wants to know about changes to the file, and make sure to call [NSFileCoordinator addFilePresenter:someObject]
  • Implement the optional presentedItemDidChange method on your file presenter.

If you do all of this right, you can write to this file from either the app or the extension, and then have presentedItemDidChange be automatically called in the other one. As a bonus you can of course read the contents of that file, so you can pass arbitrary data back and forth.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • I was trying to watch the file system changes with open() + some flags without success. I didn't know about NSFilePresenter! This worked perfectly, thanks! – Ludovic Landry Nov 20 '14 at 01:32
  • 1
    Ludovic Landry, would mind giving an example on how you implemented that communication with success please? Thanks a lot – Lee Andrew Apr 08 '15 at 20:37
  • If my containing app is not running (terminated), will `presentedItemDidChange` launch/wake the containing app? – marcelosalloum Mar 30 '16 at 11:35
  • 1
    If the app isn't running, it doesn't receive notifications. Your app would need to load the file contents when it launches anyway, though, so it doesn't need that notification to find the latest data. – Tom Harrington Mar 30 '16 at 15:19
15

There is a hack that you can use to communicate between any two apps in iOS or app and extension. The only thing - it doesn't work with NetworkExtension since Apple is blocking any I/O in it.

You can post notification to the DarwinNotificationCenter this way:

    let notificationName = CFNotificationName("com.notification.name" as CFString)
    let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

    CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, false)

In your app add observer:

    let notificationName = "com.notification.name" as CFString
    let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()

    CFNotificationCenterAddObserver(notificationCenter,
                                    nil,
                                    { (
                                        center: CFNotificationCenter?,
                                        observer: UnsafeMutableRawPointer?,
                                        name: CFNotificationName?,
                                        object: UnsafeRawPointer?,
                                        userInfo: CFDictionary?
                                        ) in

                                        print("Notification name: \(name)")
                                    },
                                    notificationName,
                                    nil,
                                    CFNotificationSuspensionBehavior.deliverImmediately)

Some links: https://github.com/choefele/CCHDarwinNotificationCenter

https://developer.apple.com/documentation/corefoundation/1542572-cfnotificationcentergetdarwinnot

https://developer.apple.com/library/content/documentation/Darwin/Conceptual/MacOSXNotifcationOv/DarwinNotificationConcepts/DarwinNotificationConcepts.html

mafonya
  • 2,152
  • 22
  • 21
  • 2
    Well, just a warning - you cannot access "self" in the callback. There is a way to access it using void pointers, but that doesn't work perfectly as well, getting "EXC_BAD_ACCESS" errors, which simply cannot be caught and processed (altough might be able with this: https://stackoverflow.com/a/55179440/2348614). What you CAN access though, is the class itself, meaning, you can store instance of your class as a static variable, from where you can call your methods. – Starwave Jul 19 '19 at 14:21
  • Be advised to first check whether the instance exists. Also, this class won't be able to deinit(), because its reference is still hard-bound into static variable, so its gona be alive in your memory. It will only be deinitialized when you overwrite/nullify that static instance variable or phone is low on memory and starts killing things left & right. – Starwave Jul 19 '19 at 14:24
  • 2
    Instead of trying access "self" you can post a NotificationCenter inside the callback, and then, add an observer anywhere in the application. works for me :) – sharon Dec 25 '19 at 12:35
  • 2
    I tried and it didn't work when the app was in background. I posted a notification from an iOS 14 widget extension to the app. When I opened the app in foreground, the notification was delivered, but not before. I think it would work with background modes. – Emma Labbé Oct 09 '20 at 19:23
9

For an alternative means of doing general-purpose bidirectional communication between host app and app extension, try MMWormhole:

http://www.mutualmobile.com/posts/mmwormhole https://github.com/mutualmobile/MMWormhole

It’s a fairly lightweight wrapper around CFNotificationCenter, and uses “Darwin” notifications to do interprocess communication (IPC).

It passes payloads back & forth using the apps’ shared container, and encapsulates even having to create the file(s) themselves.

The class (and the sample app in the repo) seem to work well, and are quite responsive.

I hope this also helps.

RonDiamond
  • 2,097
  • 2
  • 14
  • 13
2

I've been struggling with the same issue and haven't found a clean solution either. Another hacky way to solve this is to simply run a timer in the extension and check the values in the shared container preferences/database periodically and then update if required. Not elegant, but it seems to work.

jhnatow
  • 93
  • 1
  • 4