implementing multi-process shared DB in macOS using XPC - objective-c

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 ?

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.

Related

What's the difference between the 5 Magical Record setup methods?

Can anyone give a description of each of the 5 setup methods?
(void) setupCoreDataStack;
(void) setupAutoMigratingDefaultCoreDataStack;
(void) setupCoreDataStackWithInMemoryStore;
(void) setupCoreDataStackWithStoreNamed:(NSString *)storeName;
(void) setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;
What do they each do and what is the use case for each?
setupCoreDataStack
Use this when you're just getting started with MagicalRecord. This will, as the method states, set up your default Core Data stack. The pieces of the stack are well know and comprise of: NSPersistentStore, NSPersistentStoreCoordinate, NSManagedObjectModel and a default NSManagedObjectContext. At least one of each of these must be instantiated and configured properly for Core Data to work. MagicalRecord provides this single method to configure your stack with a SQLite persistent store located in /Library/Application Support//.sqlite
setupAutoMigratingDefaultCoreDataStack
When you version your model, you will need to migrate your data. This method will do the same as the previous (above) method, but will also enable Auto Migrations.
setupCoreDataStackWithInMemoryStore;
Sometimes, such as when you're writing unit tests, you want your data to go away when your app terminates. This method will also set up a CoreData stack (as mentioned above) but instead of a SQLite store, it creates a persistent store in system memory (RAM).
setupCoreDataStackWithStoreNamed:(NSString *)storeName
Sometimes you want to customize the file name where your data resides. This method does the same as the first, namely setting up the core data stack, and placing a SQLite store in the specific location, but instead of .sqlite, the store is named storeName.sqlite
setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName
This does the same as the above method, but also enabled auto migrations. You'll need to do this when you version your model and have a simple migration that simply needs to be enabled.
You can read more on Core Data migrations on Apple's Core Data Reference Documentation

Is NSPersistentStoreCoordinator executeRequest without any context, safe to fetch _objectIDs_

We have a background thread that needs to do some fetching.. but it doesnt need any data -- only the objectIDs
originally we did this using a specific newly created blank managed context just for this.
NSFetchRequest *request = [DKDocumentDetails requestAllWithPredicate:predicate inContext:ctx];
[request setResultType:NSManagedObjectIDResultType];
self.objectIDs = [DKDocumentDetails executeFetchRequest:request inContext:ctx];
...
but recently I found out, I can also do this on the PST itself, without any context AS I dont want Managed Objects, but only IDs
NSFetchRequest *request = [DKDocumentDetails requestAllWithPredicate:predicate inContext:mainctx /*used in the wrong thread but only for getting entity description*/];
[request setResultType:NSManagedObjectIDResultType];
NSError *error = nil;
self.objectIDs = [pst executeRequest:request inContext:nil error:&error];
...
so in my tests it never crashed and in the docs I dont see why it shouldnt work either... I mean I dont get unsaved stuff and I cannot get objects, but used this way...
It is faster and looks elegant but is it safe or not?
I've been thinking about your question all day. Here is what I've come up with. As others have pointed out, NSPersistentStoreCoordinator objects are not thread safe. When a bunch of NSManagedObjectContext objects on various threads use the same NSPersistentStoreCoordinator, they do so by locking and unlocking the NSPersistentStoreCoordinator.
However, you are worried about just reading data, and thread safe NSManagedObjectID data at that. Is that ok?
Well, the Apple documentation On Concurrency with Core Data mentions something similar to what you are doing:
For example, you can configure a fetch request to return just object IDs but also include the row data (and update the row cache)—this can be useful if you're just going to pass those object IDs from a background thread to another thread.
Ok, but do we need to lock the Coordinator?
There is typically no need to use locks with managed objects or managed object contexts. However, if you use a single persistent store coordinator shared by multiple contexts and want to perform operations on it (for example, if you want to add a new store), or if you want to aggregate a number of operations in one context together as if a virtual single transaction, you should lock the persistent store coordinator.
That seems to be pretty clear that if you are performing operations on a persistent store from more than one thread, you should lock it.
But wait - these are just read operations, shouldn't they be safe? Well, apparently not:
Core Data does not present a situation where reads are “safe” but changes are “dangerous”—every operation is “dangerous” because every operation has cache coherency effects and can trigger faulting.
Its the cache we need to worry about. That's why you need to lock - a read in one thread can cause data in another thread to get messed up through inadvertent cache changes. Your code never gave you problems because this is probably really rare. But its those edge cases and 1-in-1,000,000 bugs that can do the most damage...
So, is it safe? My answer:
If nothing else is using your persistent store coordinator while you read, yes, you are safe.
If you have anything else using the same persistent store coordinator, then lock it before you get the object IDs.
Using a managed object context means the locking is automatically taken care of for you, so its also a fine possibility, but it looks like you don't need to use it (and I agree it is nice to not make one just to get a few Object IDs).
From the NSPersistentStoreCoordinator docs:
Note that if multiple threads work directly with a coordinator, they need to lock and unlock it explicitly.
I would say that if you were to properly lock the PSC:
[pst lock];
self.objectIDs = [pst executeRequest:request inContext:nil error:&error];
[pst unlock];
That would be considered "safe" according to my reading of the docs. That being said, locking done internally by the MOC might be the most significant performance difference between the two approaches you have described, and if that's the case you might prefer to to just use the blank MOC as it would be less surprising when you or someone else encounter's the code later.
Related question:
Is NSPersistentStoreCoordinator Thread Safe?
There is no a good reason to NOT use a managed object context for this. The managed object context buys you a lot - it handles change management, threading, etc. etc. Using the persistent store coordinator directly loses a lot of this functionality. For example, if you have changes that have not been persisted yet to this store, you may miss them by using the persistent store coordinator directly.
Now you say the reason this is attractive to you is that you only want the managed object IDs. What it seems you really want is to find managed objects but not fire faults on them. You can do this with either a NSManagedObjectResultType or a NSManagedObjectIDResultType on your fetch request. In the case of the NSManagedObjectResultType, you would just access the objectID on your fetched objects, which will not fire a fault - thus not "getting data". This can have some performance advantages if the row cache is already populated, etc.
With all of that said, why not just use parent-child contexts to solve this?

'No database channel is available'

I have an app which connects to the internet and stores data in an SQL database. I tested with iOS4, it works completely as it should. When I upgrade to the new version though, I get an NSInternalInconsistencyException, with this as the reason:
'_obtainOpenChannel -- NSSQLCore 0x951a640: no database channel is available'
From what I can gather, my database is being accessed by something when it shouldn't be, though I can't understand where or why.
Can anyone help me locate and properly diagnose my problem?
I found something for this one:
I got the error (among some other ones, seemingly randomly appearing) while I was accessing a managed object's relationships in a different thread than the one the managed context was created in. There have been some changes with respect to concurrent access to managed objects in iOS5 (see here http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-CoreData/_index.html#//apple_ref/doc/uid/TP40010637) - and although the doc states the default behaviour should be as pre-iOS5 it apparently is not true, my code did work without problems in iOS4.2.
For now, my workaround was to do all the relationship-access thingies in the main thread, store the data in an array, and access the data I need in the other thread via that array. No more errors at least. This is not the 'nice' solution I suppose, as I should (and will) change the way I concurrently access managed objects, but I'm not going to change that in a hurry now.
This default concurrency type for NSManagedObjectContext is NSConfinementConcurrencyType, which means it can only be used by a single thread. From the documentation:
You promise that context will not be used by any thread other than the
one on which you created it.
You can instead create a managed object context that is backed by a private queue for multithreaded use:
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]
To use the managed object context from a different thread, use performBlock: (asyncronous) or performBlockAndWait: (synchronous), e.g.
__block NSArray *results;
[[self managedObjectContext] performBlockAndWait:^{
results = [[self managedObjectContext] executeFetchRequest:request error:&error];
}];
// do something with results
The documentation says you don't need to use the block API from the thread that created the managed object context.
Another option is to create separate managed object contexts for each thread.
See the iOS 5 release notes for more info.

Storing objects in iOS for later use

My app is pulling in JSON data from our web service. In all instances up til now, the information would be retained in memory or just refreshed on the fly with no need to retain anything locally other than the login token used in the API. With one of the new features we are adding, we will be taking in a group of locations, 26 max, with long, lat, radius, and name.
I also need to add 1-2 fields to this data to create a larger object. So my question is, what would be the best way to store this type of data in the iOS filesystem? Currently I have been using the NSUserDefaults, but that seems sort of limited or ill advised for larger amounts of data. Maybe not.
This data will need to be retrieved, changed or edited, and resaved. All of this while still retaining the ability to pull any of those 26 objects. Thank you in advance for reading and helping out.
For such a small amount of data (26 items) I suggest archiving.
Save to plist using NSKeyedArchiver/NSKeyedUnarchiver. Read your data from the delegate's didFinishLaunchingWithOptions, and listen for UIApplicationWillResignActiveNotification to save it.
A NSUserDefaults is a plist with features designed to store user preferences. It's often used instead a regular plist to save a couple of lines of code, which I think it's a bad idea because you get an additional complexity unrelated to your task.
If you want the login to be protected against someone stealing the device and performing forensics use the Keychain. You may want to use a wrapper and read some articles, comment if you are interested.
If you look for more features see Best way to store data on iphone but doesn't seem to be the case now.
Some code to get you started... Register to call save on app resign:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(saveMyData)
name:UIApplicationWillResignActiveNotification
object:nil];
On each object of the graph/dictionary/whatever you want to archive implement NSCoding:
- (void)encodeWithCoder:(NSCoder*)coder {
[coder encodeObject:myIvar forKey:kmyIvar];
}
- (id)initWithCoder:(NSCoder*)coder {
if((self = [super initWithCoder:coder])) {
self.myIvar = [[coder decodeObjectForKey:kmyIvar] retain];
}
return self;
}
Check out this guide on core data. It's the best way to store data locally on your device. It's a native cocoa API and it is bindings compatible. Plus, you can choose whether to store data as XML, SQLite, and Binary.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
For anything remotely large, I would use this.
I had this same question and just figured out a much better solution.
What you could do is just store the JSON string as your NSUserDefault. Then, when you reload the app use the same method (or framework utility) you used to map the JSON string to your objects the first time. This way you can still take advantage of the ease of NSUserDefaults.
If you're using RestKit to manage your web services it gets even easier. The answer on this post shows how to use RestKit's JSON parser to map from JSON to your object.
Deserializing local NSString of JSON into objects via RestKit (no network download)

Obj-C server with multiple clients

I'm a fairly novice obj-c developer and have a question on how to set up a client-server relationship. I'm designing (mostly as a hobby) a board game to be played over the internet with friends and family (think monopoly). My problem: how do I set up the appropriate client-server relationship to have one server with multiple clients?
My thinking was to have one server contain all the information on the state of the game as well as send appropriate messages to a variety of objects through Cocoa's excellent distributed objects framework. However, I can't figure out how to have one server accept multiple clients.
firstConnection = [NSConnection defaultConnection];
[firstConnection setRootObject: firstPlayer];
[[NSRunLoop currentRunLoop] run];
But then what? Is there a way to tell the run loop to stop when a client is attached? I'd like to avoid multiple threading if possible as that would be a whole new complication to learn and this project is already challenging enough!
Any help would be greatly appreciated and I'd be happy to clarify anything at all if necessary.
Thanks in advance.
Basically the strategy to take is to have the server register itself as the root object. When the client connects to the server, it sends the server a connection message (defined by the server's protocol you create) that allows the server to register that client in order to send messages to it in the future. This could be as simple as adding the client to an array; no special run loops or threads should be needed.
Here's a quick example to communicate across processes, from a test app I wrote back when I was learning DO for the first time. Once the setup is done you can add code to make the server send messages to one or more objects in the _clients array based on any event you'd like, including setting up a timer for a rough game loop.
Server:
- (void)registerClient:(byref Client *)client;
{
[_clients addObject:client];
}
- (void)awakeFromNib;
{
_clients = [[NSMutableArray alloc] init];
[[NSConnection defaultConnection] setRootObject:self];
if ( [[NSConnection defaultConnection] registerName:#"server"] == NO )
{
// error code!
}
}
Client:
- (void)awakeFromNib;
{
id theProxy;
theProxy = [[NSConnection rootProxyForConnectionWithRegisteredName:#"server" host:nil] retain];
[theProxy setProtocolForProxy:#protocol(ServerP)];
if ( theProxy == nil )
// error code!
[theProxy registerClient:self];
}
Keep in mind that there are a lot of "gotchas" in distributed objects! Start simple, even if it means developing a rough prototype of your game idea first.
Cocoa's excellent distributed objects framework
That's the first time I've seen those words together like that ;)