WatchKit Core Data Sync Up - ios

I have an app structured as follows
iOS App Writes data to Core Data which has a persistent store stored in a shared app group.
The Watch Kit extension is able to read data from Core Data that was written by the iOS app.
The issue I am having is if my iOS app writes data while my watch kit app is open I am not getting updates because the object context is not syncing with the data on the disk.
Is there a way that since my watch kit extension is only reading data to be able to refresh the context and force it to load again from the data on the disk?

My working solution was using MMWormhole to send notification (NSManagedObjectContextDidSaveNotification) from iPhone app to my watch app. In the watch app's controller i used mergeChangesFromContextDidSaveNotification: method of NSManagedObjectContext.
// in iPhone app's notification handler
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"your.group.container.identifier" optionalDirectory:nil];
[wormhole passMessageObject:notification identifier:#"your notification identifier"];
// in WKInterfaceController's awakeWithContext: method
MMWormhole *wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:#"your.group.container.identifier" optionalDirectory:nil];
[wormhole listenForMessageWithIdentifier:#"your notification identifier" listener:^(id messageObject) {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:messageObject];
}];
Then NSFetchedResultsController did all other work with UI updates.
You must implement initWithCoder: and encodeWithCoder: methods from NSCoding protocol for your NSManagedObject subclasses because MMWormhole uses NSKeyedArchiver as a serialization medium.
- (id)initWithCoder:(NSCoder *)decoder {
NSManagedObjectContext *context = ... // use your NSManagedObjectContext
NSPersistentStoreCoordinator *coordinator = ...; //use your NSPersistentStoreCoordinator
NSURL *url = (NSURL *)[decoder decodeObjectForKey:#"URIRepresentation"];
NSManagedObjectID *managedObjectID = [coordinator managedObjectIDForURIRepresentation:url];
self = [context existingObjectWithID:managedObjectID error:nil];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:[[self objectID] URIRepresentation] forKey:#"URIRepresentation"];
}

I ran into the same issue. I used - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag in the NSManagedObjectContext to get the latest data for the managed object.

Ran into similar issues. Despite creating a shared Fetched Results Controller in the App Group observing Managed Object Context changes and refreshing Managed Object Context were not feasible.
Managed Object Contexts cache a certain level of the object graph for retrieval without reading from the actual SQLite store on the disk. The only potential way to actually get live sync between the two would be sending messages across iOS app to Extension when the MOC changes and destroying/rebuilding the Core Data stack from the disk every single time, not a great solution at all.
I believe the use case for live sync between iOS and Extension at initial launch isn't a necessity. Hopefully we get a more deliberate solution to this problem in a future release.

Related

iOS: Storing data in memory rather than on disk

I'm looking for a tutorial on how to store sensitive data in memory rather than on disk for iOS (10+). I've googled but nothing really relevant has come up.
I'm familiar with most data storage options for iOS, SQLite, Plist, Core Data, User Defaults and Keychain. I know Core Data has an in-memory persistent store option but am not sure how to designate that as the one I want to use. Looking at the Apple docs and other tutorials I've only seen the instantiation of a persistent store but not declaring whether it was to be sqlite or core data or in-memory.
For example, Apple's docs on the Core Data stack:
import UIKit
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
init(completionClosure: #escaping () -> ()) {
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
completionClosure()
}
}
}
This question seems to point in the right direction (just the code initially posted)
Save In-Memory store to a file on iOS
- (NSPersistentStore *)addPersistentStoreWithType:(NSString *)storeType configuration:(NSString *)configuration URL:(NSURL *)storeURL options:(NSDictionary *)options error:(NSError **)error;
Is it just that, passing a type? And to follow-up, once the app closes, the in-memory data is released?
Thanks
Since you're using NSPersistentContainer, you tell Core Data what kind of store to use with an instance of NSPersistentStoreDescription. It has a property called type that accepts values like NSInMemoryStoreType. Set up a description and then assign that to the container's persistentStoreDescriptions property, and you'll get an in-memory store. The method you mention would work but would require changing your Core Data setup to drop NSPersistentContainer.
It exists, as the name implies, only in memory, so anything stored there disappears when the app exits.

Send array of NSManagedObject to watchOS in sendMessage: reply handler

The following is a test method for communicating between the watchOS and iOS components of my app:
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {
NSArray *responseArray = #[#"hello", "world"];
NSDictionary *responseDict = #{#"response": responseArray};
replyHandler(response);
}
This works perfectly - in the reply handler on the watch I can log the contents of responseDict and see the objects #"hello" and #"world". However, if I change responseArray to contain NSManagedObject instances (for sending actual data to the watch), the sendMessage error handler is triggered with an error saying Payload could not be delivered. Before I change my database structure to include a uuid for the entities I need to send (so I can send them represented by their UUID in NSString format), I just wanted to check: is it actually possible to send NSManagedObject instances to watchOS?
No, it's not possible to send NSManagedObject instances between contexts, threads, or devices.
A managed object only exists within its managed object context. Its data would be nil, if you tried to access or copy it outside its context.
If your Core Data persistent store is on the phone, but you want to display a managed object's data on the watch, you'd first to move the data from the managed object into another type (e.g., a dictionary), and then send that data to the watch.
See this answer for more details.

How to remove single chat through xmpp in iOS?

I am working on chatting app in iOS using xmpp and ejabberd . I am not able to remove single message.
Is this correct method - (void)removeResources:(NSSet *)value to remove chat? And what parameter I need to remove chat?
Or do I need to remove entry from core data? On Quick Blox I found this method:
NSSet *mesagesIDs = [NSSet setWithObjects:#"54fdbb69535c12c2e407c672", #"54fdbb69535c12c2e407c673", nil];
[QBRequest deleteMessagesWithIDs:mesagesIDs
How to use this in my project without quickblox?
If you have XMPPMessageArchiving_Message_CoreDataObject. i think this object is used to display data in UITableView so you can direct removed that object from core data using below code. Here i am showing to delete loop of messages.
NSManagedObjectContext *moc = [self managedObjectContext];
for (XMPPMessageArchiving_Message_CoreDataObject *message in messages)
{
[moc deleteObject:message];
}
Hope this help you.

iOS Core Data data insert

I have an app which asynchronously downloads a JSON file and then it should insert those objects within Core Data for persistent storage. Regarding the insert, is it a good idea to do it from the main thread? What if there are thousands of objects? Should I do the inserts on a different thread? Could you provide me with some snippets regarding this matter? Regarding the fetching of the objects after I've saved them, should I also use a different thread?
My code for inserting into Core Data is:
- (void) insertObjects:(NSArray*)objects ofEntity:(NSString *)entityName
{
NSString *key;
NSManagedObject *managedObject;
NSError *error;
for(NSDictionary *dict in objects){
managedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:_managedObjectContext];
for(key in dict){
[managedObject setValue:dict[key] forKey:key];
}
}
[_managedObjectContext save:&error];
}
PS: The objects are of the same entity. The project runs on iOS 7.0 or higher.
Since I can't comment yet..
What iOS Versions do you plan to support? If 5 and higher, this might help Concurrency stack
Summary of the link:
you create a context of private concurreny type to access your physical data
based on this you create a context of main concurreny type
on top of this you use private concurrency type stores again.
Don't forget so save in every store, otherwise, the data seems to be saved while the app is running, but after restart it is lost.
And yes, you want to do it an extra thread, since it would otherwise block the UI if there are to many items.

Switching between Core Data stores

I am currently updating an app to use Core Data. The app you could say is a "database viewer", only one database is able to be viewed at a time. Each database is kept in its own separate folder. Currently the data is downloaded and stored as a set of plist files.
In the new version I need to convert these plist databases into Core Data stores (one store for each database.) I've already setup the methods that create the separate store files, and crete the entities. The problem is that all the entities are saved to the first database I created, not to the "current" or "lastly created" file.
The basic process I'm using is:
//For each database {
//Create the sqlite file and set up NSManagedObjectContext
[MagicalRecord setupCoreDataStackWithStoreNamed:
[NSURL fileURLWithPath:
[NSString stringWithFormat:#"%#/%#/%#.sqlite",
dirPath, directory, directory]]];
NSManagedObjectContext *managedObjectContext =
[NSManagedObjectContext MR_contextForCurrentThread];
//Iterate through all the plist files and create the necessary entities.
//Save new entities to file
[managedObjectContext MR_save];
//Clean up all cashes
[MagicalRecord cleanUp];
}
How would one properly switch between stores, essentially "reseting" everything between each switch. Preferably (if possible) using magical record.
EDIT:
I've found out a portion of the problem, and removed most of the unwanted behavior. It turns out, you can't reliably call [MagicalRecord cleanUp] on a background thread. Also, It isn't doing what I think it should (see below). I ended up calling back to the main thread after each "save" to reset the Core Data stack. Doing this creates a new context for the first three databases. after that, it duplicates the context from the database three databases ago. So the same three contexts are used in a loop.
This is what I currently have;
I start the process by creating a background thread and run the code to create a single database in the background:
backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self createSQLiteDatabase:updateList];
});
Then creating the stack and database:
- (void)createSQLiteDatabase:(NSArray *)updateList
{
NSString *directory = [updateList objectAtIndex:0];
[MagicalRecord setupCoreDataStackWithStoreNamed:
[NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/%#/%#.sqlite",
dirPath, directory, directory]]];
NSManagedObjectContext *managedObjectContext =
[NSManagedObjectContext MR_contextForCurrentThread];
//Check to see if the stack has reset
NSLog(#"Before:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);
//Create and add entities to context...
//Prepare for next loop
NSLog(#"After:%i", [[Competition MR_findAllInContext:managedObjectContext] count]);
[managedObjectContext MR_saveNestedContexts];
[NSManagedObjectContext MR_resetContextForCurrentThread];
NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:updateList];
[temp removeObjectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^(void){
[self shouldContinueUpdating:temp];
});
Then reset everything and repeat for all databases:
- (void)shouldContinueUpdating:(NSArray *)databases
{
//preform cleanup on main thread and release background thread
[MagicalRecord cleanUp];
dispatch_release(backgroundQueue);
if ([databases count] != 0) {
backgroundQueue = dispatch_queue_create("com.BrandonMcQuilkin.myQueue", NULL);
dispatch_async(backgroundQueue, ^(void) {
[self createSQLiteDatabase:databases];
});
}
}
With the two NSLogs, I get this in the console: (using six databases, the pattern is the same no matter how many databases I convert.)
//First Loop
Before:0
After:308
//Second Loop
Before:0
After:257
//Third Loop
Before:0
After:37
//Fourth Loop
Before:308
After:541
//Fifth Loop
Before:257
After:490
//Sixth Loop
Before:37
After:270
... Keep adding to each of the three contexts.
And [MagicalRecord cleanUp] isn't doing what It say it's doing. Here is what the method is supposed to do.
+ (void) cleanUpStack;
{
[NSManagedObjectContext MR_cleanUp];
[NSManagedObjectModel MR_setDefaultManagedObjectModel:nil];
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:nil];
[NSPersistentStore MR_setDefaultPersistentStore:nil];
}
But It turns out that the NSStoreCoordinator every time I save, is the same coordinator, in the same memory location, and each store is hanging around. Something is not working right...
MagicalRecord may not be the best tool for this job for you...
First, let's correct your usage of the setupCoreDataStackWithStoreNamed: method. The parameter takes an NSString, not a URL, nor a file path. MagicalRecord will pick the proper path for you and create your store there. your resulting sqlite file is likely to be named with the path you intended it to be.
Next thing, you'll need to dynamically create your CoreData model for this file. This is kind of tough, but possible. You'll need to traverse these plist files, and interpret entities, attributes and relationships, and create corresponding NSEntityDescriptions, NSAttributeDescriptions and NSRelationshipDesctiptions and populate an NSManagedObjectModel "manually". Youll want to look for the method
- [NSManagedObjectModel setEntities:(NSArray *)]
as well as the creation methods for NSEntityDescription, NSAttributeDescription and NSRelationshipDescription.
You'll also want to save this model somewhere so you don't have to recreate it every time. Luckily, it conforms to NSCoding, so you should just be able to save it to disk.
After that, you'll probably want to populate your data. From here, MagicalRecord can help you. I suggest looking at the Importing Data Made Easy blog post I wrote for Cocoa is My Girlfriend
If you want to "switch stores", which I guess means you want to create a new store for each plist file you've got, then you're going to have to tear down the entire Core Data stack for each file. If you manage to use MagicalRecord for this project, you'll need to look at [MagicalRecord cleanUp], and start over. If each model was the same, you could get by with releasing your Persistent Store Coordinator, and creating a new one to your store. But since your "schemas" will probably be different, you'll just want to scratch everything and start over.
Turns out The problem I'm having is because of a bug in MagicalRecord. I've submitted a git issue here: https://github.com/magicalpanda/MagicalRecord/issues/270

Resources