iOS Sync in background thread - objective-c

i need some advice concerning background synchronisation...
At the moment I have an app which starts sync in background after app has started and then continues via timer every 2 minutes. Now I want to enable the user to manually start sync process in case of impatience... but ensure that there is no currently running sync.
If there is an sync running the users request should be "redirected" to this sync if not a new sync shall be started, but prevent the auto sync from being starts while manual sync is in progress.
Unfortunately I don't have an idea on how to do this....
Thx for your help!
BR Boris

You do not give much detail about how you are trying to do this, but a simple solution should be this one:
add a flag to your controller: isAlreadySyncing;
when your start syncing, set the flag; reset it when finished;
when the user starts syncing out of impatience, check the flag: if it is true, do not do anything; otherwise, start syncing (and also set the flag, as above);
when your timer fires and tries to sync, check the flag: if it is set, do not to anything.
Hope this helps.

I think you have different controllers.
What you need to do is use NSUserDefaults.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:#"YES" forKey:#"is_sync_auto"];
[defaults setValue:#"NO" forKey:#"is_sync_manual"];
[defaults synchronize];
when the user starts syncing out of impatience, check the defaults: if it is YES, do not do anything; otherwise, start syncing (and also set the flag, as above).
To redirect the request, you can create a shared instance and then invoke it again.
OR
A better way would be to create a singleton class.
Let that singleton class handle the sync operation, and as no more then one instance can be created, it will solve your problem.
+ (instancetype)sharedConnection {
static ConnectionClass *_sharedConnection = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shared_sharedConnection = [[ConnectionClass alloc] init];
});
return _sharedConnection;
}

Related

How to Persist Application State in cocoa

I need to store the state of application at application termination time, so that when user re-run app, App run from the state in which it was closed last time. It is some kind of restoring app but restore methods called when app close unexpectedly. But i need to restore app each time when it close unexpectedly of user close it manually.
I just need to store the App UI not the data of application.
Any idea would be helpful for me.
Thanks
You can persist the state in any of the available methods like:
i. NSUserDefaults
Example:
//saving
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:#"testBool"];
//retrieving
[defaults boolForKey:#"testBool"];
ii. Serializing the state object.
iOS 4 iPhone Data Persistence using Archiving
Correct way to save/serialize custom objects in iOS
iii. Saving as a plist file
Example:
NSMutableDictionary *stateDictionary = [NSMutableDictionary dictionary];
//set state
...
//saving
[stateDictionary writeToFile:<filePath> atomically:YES];
//retrieve
stateDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:<filePath>]
iv. Using sqlite or Core-Data
(Most probably not needed unless the
state of your app is in some kind of a object relational model)
UPDATE:
For preserving the UI state of windows,
Check this link and under the heading USER INTERFACE PRESERVATION.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
//saveData
return NSTerminateNow;
}
If you want to save NSWindow position , you can use [window saveFrameUsingName:#"myWindow"];
and use
[window setFrameAutosaveName:#"myWindow"]; # the app launch.

How to ensure UIManagedDocument is ready before NSFetchedResultsController method is called?

I am totally stuck on this one. My basic problem is that my:
- (NSFetchedResultsController *)fetchedResultsController
Method is crashing when it tries to read my Core Core entity, as the managedObjectContext / UIManagedDocument is nil. At the moment I think it is because my UIManagedDocument is not open / ready. So for the last 3 hours I have been trying to make it so my delegate method is not fired until the document is open.
This is the code that I am using to get the document:
if (!self.document) {
[[CATManagedDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
}];
}
This works fine at any other place in my app, but it seems as the opening process is just not quick enough for the delegate methods in my tableView.
Links I have looked at so far:
http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/
Executing Code Block In Place Of #selector
About calling of dispatch_queue_t and dispatch_sync
Grand Central Dispatch (GCD) vs. performSelector - need a better explanation
GCD to perform task in main thread
iOS - how to be notified when a thread (using GCD) ends it's job
I have tried: Blocking main thread until I get NSNotification (set up in CATManagedDocumentHandler) & Blocking main thread until I get a block call back.
Neither of these work. My app just freezes. Am I think about this wrongly? How can I get the delegate method to wait until my document is open / ready? Or is there a different approach I should be taking with this?
Thanks
Carl.
ok, when your app first starts, I would suggest checking whether 1. your database exists (if it doesn't, you alloc init it) and 2. if the document doesn't exist (on disk), is closed or is open.
here's how you could do this:
looks like you are using a singleton for your database, so when your first view controller comes up, check whether the Managed Document has been alloc inited, so in ViewWillAppear:
if (!dataBase)
{
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Your DB Name"];
self.dataBase=[[UIManagedDocument alloc]initWithFileUrl:url];
}
so now if the database (UIManagedDocument) has been alloc inited, it still does not mean that the actual database file has been created on disk. You have to check for it as follows (and you could do this in a setter for your database or in ViewDidLoad, but don't do it in another thread because it won't work)
so you are checking for 3 cases here: does not exist
if (![[NSFileManager defaultManager]fileExistsAtPath:[yourManagedDocumentFromSingleton.fileUrl path])
{
[[NSFileManager defaultManager]fileExistsAtPath:[yourManagedDocumentFromSingleton saveToUrl:[[NSFileManager defaultManager]fileExistsAtPath:[yourManagedDocumentFromSingleton.fileUrl forSaveOperation:UIDocumentSaveForCreating completionHandler:
^(BOOL success){ now you can use your uidocument and its managed object context}];
}
if file does exist but is closed:
else if (yourManagedDocumentFromSingleton.documentState==UIDocumentStateClosed)
{
[yourManagedDocumentFromSingleton openWithCompletionHandler:
^(BOOL success) {use your document here}];
}
finally if the document is already open, just use it:
else if (yourManagedDocumentFromSingleton.documentState=UIDocumentStateNormal)
{
//do whatever with the document
}
one important thing to mention is that UIDocument is not thread safe. So it must be used and checked in the same thread that it was created (presumably main thread here). Otherwise it will not work.
I don't know the exact structure of your view controller or singleton but if you follow these steps it will work.
PS. Also make sure that once your doc is up and running and you're adding items to it or removing, save it after each operation so your NSFetchedResultsController gets updated. CoreData does have autosave but I found that I had to manually save for things to work properly. You can save with (from previous method):
forSaveOperation:UIDocumentSaveForOverwriting

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

ios coredata updates not seen on different managed object contexts - data is different between contexts

We're having this issue where different threads see different data on the same records but with different managed object contexts (moc). Our app syncs in the background to a server API. All of the syncing is done on it's own thread and using it's own moc. However, we've discovered that when data gets updated on the main moc that change in data is not shown in the background moc. Any ideas what could be happening? Here's some more details: we're using grand central dispatch like so to put the sync operations on it's own thread: We've checked which queue things are running on and it all is happening on the queue expected.
- (void) executeSync; {
dispatch_async(backgroundQueue, ^(void) {
if([self isDebug])
NSLog(#"ICSyncController: executeSync queue:%# \n\n\n\n\n", [self queue]);
for(id <ICSyncControllerDelegate> delegate in delegates){
[delegate syncController:self];
}
if([ICAccountController sharedInstance].isLoggedIn == YES && shouldBeSyncing == YES) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 300ull * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self executeSync];
});
}
});
}
here's how we create the background moc and we've confirmed that it's created on the background queue.
- (NSManagedObjectContext*)backgroundObjectContext {
if (_backgroundObjectContext)
return _backgroundObjectContext;
_backgroundObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundObjectContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
[_backgroundObjectContext setStalenessInterval:0.0];
return _backgroundObjectContext;
}
I should add that our background moc is requerying for data and those records returned from that action still have the old values for some fields. How does the background moc get the current data that was already saved by the main moc? I thought just by requerying I would get the current state of these records..
by requerying I mean the following:
The background MOC is executing another "query" to get "fresh" data after the records have been changed by the main moc, yet the data has old values - not the updated values seen in the main moc.
+ (NSArray *)dirtyObjectsInContext:(NSManagedObjectContext *)moc {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(memberships, $m, $m.category.name == %# AND $m.syncStatus > %d).#count > 0", MANAGED_CATEGORY_FAVORITES, ManagedObjectSynced];
return [self managedObjectsWithPredicate:predicate inContext:moc];
}
Your help is hugely appreciated as we've been trying to figure this out, or find a work around that doesn't include ditching our threads for days now.
That's how it's supposed to work -- indeed, an important role of the managed object context is to protect you from changes to the data made in other threads. Imagine the havoc that would result if you had a background thread modifying the same objects that the main thread was using without some sort of synchronization scheme.
Read Communicating Changes Between Contexts to learn how to merge changes from one context into another.
I use the following code to listen for changes on context 2, so that context 1 keeps up to date:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:context1
selector:#selector(contextTwoUpdated:)
name:NSManagedObjectContextDidSaveNotification
object:context2];
it causes this method to be called on context 1, and i invoke the merge method:
- (void)contextTwoUpdated:(NSNotification *)notification {
[context1 mergeChangesFromContextDidSaveNotification:notification];
}
a side effect of this is any NSFetchedResultsController that is attached to context1 will send a variety of messages to its delegate informing it of the changes,
i've never tried listening both ways while the user changes the object and you update them on the user from behind - i suspect you may have to manage merges if that's the case, since it's one-way (and all user driven) for me i assume all updates to be valid

NSFetchedResultsController not displaying changes from background thread

My app is using an NSFetchedResultsController tied to a Core Data store and it has worked well so far, but I am now trying to make the update code asynchronous and I am having issues. I have created an NSOperation sub-class to do my updates in and am successfully adding this new object to an NSOperationQueue. The updates code is executing as I expect it to and I have verified this through debug logs and by examining the SQLite store after it runs.
The problem is that after my background operation completes, the new (or updated) items do not appear in my UITableView. Based on my limited understanding, I believe that I need to notify the main managedObjectContext that changes have occurred so that they may be merged in. My notification is firing, nut no new items appear in the tableview. If I stop the app and restart it, the objects appear in the tableview, leading me to believe that they are being inserted to the core data store successfully but are not being merged into the managedObjectContext being used on the main thread.
I have included a sample of my operation's init, main and notification methods. Am I missing something important or maybe going about this in the wrong way? Any help would be greatly appreciated.
- (id)initWithDelegate:(AppDelegate *)theDelegate
{
if (!(self = [super init])) return nil;
delegate = theDelegate;
return self;
}
- (void)main
{
[self setUpdateContext:[self managedObjectContext]];
NSManagedObjectContext *mainMOC = [self newContextToMainStore];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:updateContext];
[self setMainContext:mainMOC];
// Create/update objects with mainContext.
NSError *error = nil;
if (![[self mainContext] save:&error]) {
DLog(#"Error saving event to CoreData store");
}
DLog(#"Core Data context saved");
}
- (void)contextDidSave:(NSNotification*)notification
{
DLog(#"Notification fired.");
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[delegate managedObjectContext] performSelectorOnMainThread:selector
withObject:notification
waitUntilDone:YES];
}
While debugging, I examined the notification object that is being sent in contextDidSave: and it seems to contain all of the items that were added (excerpt below). This continues to make me think that the inserts/updates are happening correctly but somehow the merge is not being fired.
NSConcreteNotification 0x6b7b0b0 {name = NSManagingContextDidSaveChangesNotification; object = <NSManagedObjectContext: 0x5e8ab30>; userInfo = {
inserted = "{(\n <GCTeam: 0x6b77290> (entity: GCTeam; id: 0xdc5ea10 <x-coredata://F4091BAE-4B47-4F3A-A008-B6A35D7AB196/GCTeam/p1> ; data: {\n changed =
The method that receives your notification must indeed notify your context, you can try something like this, which is what I am doing in my application:
- (void)updateTable:(NSNotification *)saveNotification
{
if (fetchedResultsController == nil)
{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
//Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
}
else
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
// Reload your table view data
[self.tableView reloadData];
}
}
Hope that helps.
Depending on the specifics of what you are doing, you may be going about this the wrong way.
For most cases, you can simply assign a delegate using NSFetchedResultsControllerDelegate. You provide an implementation for one of the methods specified in "respondingToChanges" depending on your needs, and then send the tableView a reloadData message.
The answer turned out to be unrelated to the posted code which ended up working as I expected. For reasons that I am still not entirely sure of, it had something to do with the first launch of the app. When I attempted to run my update operation on launches after the Core Data store was created, it worked as expected. I solved the problem by pre-loading a version of the sqlite database in the app so that it did not need to create an empty store on first launch. I wish I understood why this solved the problem, but I was planning on doing this either way. I am leaving this here in the hope that someone else may find it useful and not lose as much time as I did on this.
I've been running into a similar problem in the simulator. I was kicking off an update process when transitioning from the root table to the selected folder. The update process would update CoreData from a web server, save, then merge, but the data didn't show up. If I browsed back and forth a couple times it would show up eventually, and once it worked like clockwork (but I was never able to get that perfect run repeated). This gave me the idea that maybe it's a thread/event timing issue in the simulator, where the table is refreshing too fast or notifications just aren't being queued right or something along those lines. I decided to try running in Instruments to see if I could pinpoint the problem (all CoreData, CPU Monitor, Leaks, Allocations, Thread States, Dispatch, and a couple others). Every time I've done a "first run" with a blank slate since then it has worked perfectly. Maybe Instruments is slowing it down just enough?
Ultimately I need to test on the device to get an accurate test, and if the problem persists I will try your solution in the accepted answer (to create a base sql-lite db to load from).