Search code examples
objective-cdatabasemacoscocoapreference

implementing multi-process shared DB in macOS using XPC


My Goal is to develop robust, coherent, and persistent DB that can be shared between processes, just list Windows Registry.

On a previous question I advised not to use CFPreferences (and NSUserDefaults) due to the following reason

Current versions of macOS have a great deal of difficulty, and sometimes outright refuse, to update the values in one process with the values set by a second process.

Alternatively, I advised to use the following approach:

To have one process responsible for all of the shared values and the other processes get/set those values via XPC.

The XPC is quite new to me but from what I've read so far, it seems to use GCD queues for each connection, so how can I keep coherency if there are multiple processes asking to access the same DB for R/W operations (how can I enforce single thread to execute items in all queues).

Furthermore, I'd like to make this DB to fulfill ACID requirements, how can I achieve this ?


Solution

  • Here's my suggestion, and the solution I use in my applications.

    (1) Create a named XPC service.

    If you need to connect with your service from multiple apps, you'll need to name and register your service app with launchd.

    (XPC make it pretty easy to create an anonymous service used only by your app, but connecting from other apps gets a little trickier. Start with the Daemons and Services Programming Guide.)

    Note that in my solution, I already had a user agent registered with launchd, so this was just a matter of moving on to step (2).

    (2) Add XPC message handlers to get and set the values you want to share.

    - (void)queryPreferenceForKey:(NSString*)key withReply:(void(^)(id value))reply
    {
        reply([[NSUserDefaults standardUserDefaults] objectForKey:key]);
    }
    
    - (void)setPreferenceValue:(id)value forKey:(NSString*)key withReply:(void(^)(BOOL changed))reply
    {
        BOOL changed = NO;
        id previous = [[DDUserPreferences standardUserDefaults] objectForKey:key];
        if (!OBJECTS_EQUAL(previous,value))
            {
            [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
            changed = YES;
            }
        reply(changed);
    }
    

    (3) There is no step 3.

    Basically, that's it. The NSUserDefault class is thread safe, so there are no concurrently issues, and it automatically takes care of serializing the property values and synchronizing them with the app's persistent defaults .plist file.

    Note: since this is based on NSUserDefaults, the value objects must be property-list objects (NSString, NSNumber, NSArray, NSDictionary, NSDate, NSData, ...). See Preferences and Settings Programming Guide.