App Group data sharing does not work sometimes between HostApp and Service extension.

Hi There, My app is a legacy project built with Objective C. The host app shared data with the service extension by using

NSUserDefaults *userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.myapp.project"];

and it worked until a customer recently reported a bug (iOS 18.6). After debugging, I found that data sharing from the host app to the service extension was not working correctly.

The host app updated a field's value, but the service still used the old or stale value, causing the issue.

HostApp saved info

2026-06-01 13:44:07.020 [INFO] (VMP)(ThreadID:  0x10a85c000): "Saved Vomo information {
    "EXT_AP_IP" = 1c28af0f9d73;
    "EXT_PING_DND" = 0;
    "EXT_PING_USER_NAME" = aaa08AA541F8;
    "EXT_SERIAL_ACK_TIME" = "2026-06-01 20:44:07 +0000";
    "EXT_SERIAL_NO" = 689;
    "EXT_SERVER_NAME" = "10.xxx.xxx.182";
    "EXT_VOICE_LOGIN" = 1;
}"

Service extension read value:

2026-06-01 13:46:09.678 [INFO] (VMP) - (EXTENSION)(ThreadID:  0x1050a41d0): "start Vomo with Server: [10.xxx.xxx.79] and userName [aaa08AA541F8]"

I can see the value shared from host app is:

10.xxx.xxx.182, but service extension still took the stale value 10.xxx.xxx.79

First I thought it is synchronized issue, however, apple deprecated those API,

CFPreferencesAppSynchronize((__bridge CFStringRef)@"group.com.myexample.project"); How to ensure the shared value successfully delivered to service extension?

Thanks.

Answered by DTS Engineer in 896477022
yes for now

OK.

If this were something that was commonly reproducible then it might be worthwhile digging deeper into the user defaults subsystem to work out why it’s failing. However, the fact that only one person is seeing it presents you with a choice:

  • You can stick with user defaults and try to help that one specific person.
  • You can move off user defaults.

If I were in your shoes I’d probably do the latter. As I mentioned above, user defaults is best suited for user preferences, as oppose to stuff that’s critical for your app.

Switching to a file might not be trivial depending on the specifics of your app:

  • It’s best if you can set things up so that only your main app writes the file and your app extensions read it. That way you don’t have worry about merge conflicts.
  • Write a file of preferences is remarkably easy: Historically you’d just call -[NSDictionary writeToURL:atomically:]. That’s deprecated, but you can achieve the same result by serialising a property list dictionary to data and writing that using -[NSData writeToURL:options:error:].
  • Make sure you use an atomic writing mechanism. Without that, the reader might see inconsistent state.
  • It’s easiest if you don’t cache the file’s state in the app extension. That way you don’t have to worry about cache invalidation.
  • But if you must cache the file’s state, you can use a Darwin notification to flush that cache.
  • Make sure the file’s data protection is appropriate for all the app extensions that read it.

If things are more complex than this — for example, if you need to write to the file from multiple processes — then lemme know and we can talk about more complex options. However, in common cases the approach above is both simple and effective.

And please do reply back here if you have follow-up questions about any of this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Are you seeing reports of this from just this one user?


Regardless, I suspect that you’d be better off not storing this value in user defaults. User defaults is, as the name suggests, intended for user preferences. If some data is critical to the operation of your product — and that certainly seems to be the case here — it’s better to store it in a file that you manage directly. And if you want to share that file from your app to your appex, you can put it in the container for the app group you already have. Use -[NSFileManager containerURLForSecurityApplicationGroupIdentifier:] to get that container’s location on disk.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn.

yes for now, I would suspect there will be more cases coming in

yes for now

OK.

If this were something that was commonly reproducible then it might be worthwhile digging deeper into the user defaults subsystem to work out why it’s failing. However, the fact that only one person is seeing it presents you with a choice:

  • You can stick with user defaults and try to help that one specific person.
  • You can move off user defaults.

If I were in your shoes I’d probably do the latter. As I mentioned above, user defaults is best suited for user preferences, as oppose to stuff that’s critical for your app.

Switching to a file might not be trivial depending on the specifics of your app:

  • It’s best if you can set things up so that only your main app writes the file and your app extensions read it. That way you don’t have worry about merge conflicts.
  • Write a file of preferences is remarkably easy: Historically you’d just call -[NSDictionary writeToURL:atomically:]. That’s deprecated, but you can achieve the same result by serialising a property list dictionary to data and writing that using -[NSData writeToURL:options:error:].
  • Make sure you use an atomic writing mechanism. Without that, the reader might see inconsistent state.
  • It’s easiest if you don’t cache the file’s state in the app extension. That way you don’t have to worry about cache invalidation.
  • But if you must cache the file’s state, you can use a Darwin notification to flush that cache.
  • Make sure the file’s data protection is appropriate for all the app extensions that read it.

If things are more complex than this — for example, if you need to write to the file from multiple processes — then lemme know and we can talk about more complex options. However, in common cases the approach above is both simple and effective.

And please do reply back here if you have follow-up questions about any of this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks Quinn,

With current implementation.

  1. yes, main app is in charge of writing, and extension is sort of read only, very rare edge case to write back.
  2. the architecture like this: app into background | special event occurred --> load User defaults and write:
-----
 write small set info to user defaults. // 5 pairs of key / value

    NSMutableDictionary* sharedDefaults = [[NSMutableDictionary alloc] init];
    NSDictionary *saved = [SharedInfoManager getVomoInfo];
    if(saved != nil)
    {
        [sharedDefaults addEntriesFromDictionary:saved];
    }
    // 
    [sharedDefaults setObject: @"Value" forKey:ONEOF_SHARED_INFO_PROPERTY_KEYS]; 
    [SharedInfoManager setVomoInfo:sharedDefaults];
-----

+ (NSDictionary *)sharedVomoInfo
{
    return [SharedInfoManager getVomoInfo];
}

+ (NSDictionary *)getVomoInfo
{
      return [self getSharedInfoWith:SHARED_VOMO_INFO];
}

+ (NSDictionary*)getSharedInfoWith:(NSString *)key
{
    NSUserDefaults *userDefault = [SharedInfoManager getSharedInfo];
    return [userDefault dictionaryForKey:key];
}

+ (NSUserDefaults*)getSharedInfo
{
    return [[NSUserDefaults alloc] initWithSuiteName:@"group.com.myexample.project"];
}

+ (void)setVomoInfo:(NSDictionary *)info
{
    [self setSharedInfo:SHARED_VOMO_INFO withValue:info];
}
+ (void)setSharedInfo:(NSString*) key withValue:(NSDictionary*)info
{
    NSUserDefaults* defaults = [SharedInfoManager getSharedInfo];
    [defaults setObject:info forKey:key];
    [defaults synchronize];
}

if I am not mistaken, It could be something like

{
     // return [self getSharedInfoWith:SHARED_VOMO_INFO];

NSData *data = [NSData dataWithContentsOfFile:path options:0 error:&error];

NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data
                                                             options:NSJSONReadingAllowFragments
                                                               error:&error];

}

+ (void)setVomoInfo:(NSDictionary *)info
{
    //[self setSharedInfo:SHARED_VOMO_INFO withValue:info];
NSData *dataFromDict = [NSJSONSerialization dataWithJSONObject:dict
                                                       options:nil
                                                         error:&error];

[NSData writeToURL:options:error:]

}

is it thread safe? again, much appreciated your help for this.

very rare edge case to write back.

Hmmm, that’s a potential complication. That’s true for the file-based solution that I’m suggesting, but it’s also true for your current solution based on user defaults. If the app and the appex both try to make a change, there’s no specific step to reconcile those changes. The changes end up being applied atomically, putting you in a last writer wins situation.

If that’s acceptable then cool. But if not, things get substantially more complex.

is it thread safe?

If you pass the NSDataWritingAtomic option to that -writeToURL:options:error: call, then yes.

Well, yes in the sense that I described above, in that the last writer wins.

Also, think carefully about data protection here. If there’s any chance that either the app or the appex need to access this file while the device is locked, you have to make sure that its data protection allows that. -writeToURL:options:error: has more options that let you control the file’s data protection.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

App Group data sharing does not work sometimes between HostApp and Service extension.
 
 
Q