i use a combination of queue and resultscontroller to update and display some coredata objects.
in my uitableviewcontroller i call every X second a method in my main controller object.
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(test:) userInfo:nil repeats:YES];
}
- (void)test:(NSTimer*)theTimer {
[[MainController instance] updatePersons];
}
In this method a custom NSOperation object will be added to my main Q.
- (BOOL)updatePersons {
UpdatePersonsOperation* u = [[UpdatePersonsOperation alloc] init];
[u setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operationQ u];
[u release];
The operation itself creates a new managedobjectcontext and tries to download some xml from the web and tries to update the coredata database... (This code works!)
In my main controller i receive the context changed message and i use mergeChangesFromContextDidSaveNotification to merge and update my main object context. All resultscontroller use this main object context.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationManagedObjectContextDidSave:) name:
NSManagedObjectContextDidSaveNotification object:nil];
Actually everything works but when i insert a new object inside a NSOperation it takes 4-6 seconds till the UI updates and displays this new object... Furthermore the UI blocks... Its not possible to scroll or trigger a other interaction...
When i don't use a queue and i put my code to download and update the objects into a method inside my uitableviewcontroller class and i use this
[NSThread detachNewThreadSelector:#selector(codeFromUpdatePersonsOperation) toTarget:self withObject:nil];
Everything works very well without any delay or UI freeze...
Can someone explain me this behavoir?
Thanks
Another problem may have been that updating the UI needs to take place on the Main thread. I was experiencing the same issue that you reported, and eventually figured out that if you call
[target performSelectorOnMainThread:#selector(methodToUpdateUI:) withObject:dataFromRetrievalOperation waitUntilDone:NO];
This will cause the UI to update immediately when the thread has finished processing. Otherwise, it waits about 5 seconds before the UI animations take place.
Related
It seems that UIPageViewController is holding the initial content view controller forever.
For example:
DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
The startingViewController is never released until the pageViewController itself it released.
To reproduce this bug, just create a new project in XCode using the Page-Based Application template. And add 3 lines of code into DataViewController.m
#property NSInteger debugIndex; // file scope
NSLog(#"DataViewController[%d] created", self.debugIndex); // in viewDidLoad
NSLog(#"DataViewController[%d] dealloc", self.debugIndex); // in dealloc
And when you scroll the demo App in vertical orientation, you'll get logs like this:
DataViewController[0] created
DataViewController[1] created
DataViewController[2] created
DataViewController[1] dealloc
DataViewController[3] created
DataViewController[2] dealloc
DataViewController[4] created
DataViewController[3] dealloc
DataViewController[5] created
DataViewController[4] dealloc
DataViewController[6] created
DataViewController[5] dealloc
DataViewController[0] is never deallocated.
Any ideas about this?
Thanks!
Are you using transitionStyle UIPageViewControllerTransitionStyleScroll? I encountered the same or a similar problem which seemed to disappear when using page curl animations instead.
The problem was compounded for me because I was allowing a UISliderBar to set the position in the content. So on change of the UISliderBar, I was calling setViewControllers:direction:animated:completion: which caused more and more view controller references to get "stuck" in my UIPageViewController.
I am also using ARC. I have not found an acceptable way to force the UIPageViewController to let go of the extra view controller references. I will probably either end up using the page curl transition or implementing my own UIPageViewController equivalent using a UIScrollView with paging enabled so I can manage my own view controller cache instead of relying on UIPageViewController's broken view controller management.
I'm not sure you still got the problem, but I had the same problem and I found the solution.
I don't know the reason, but it works.
I'm setting the first viewController right after addSubview, rather than before addChlidViewController.
-(void)settingPageViewController{
if (!self.pageViewController) {
self.pageViewController = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
[self addChildViewController:self.pageViewController];
[self.pageViewController didMoveToParentViewController:self];
[self.containerView addSubview:self.pageViewController.view];
[self.pageViewController setViewControllers:#[[self viewcontrollerAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
}
and the first viewController will dealloc in the right time.
also, I found if call
[self.pageViewController setViewControllers:#[[self viewcontrollerAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^(BOOL finished){
NSLog(#"finished : %d",finished);
}];
before addSubView and the completion block will not call.
and I reckon this block is the reason why the first viewController didn't dealloc.
I'll go find out why it didn't callback, and improve the answer~
cheers
After a few attempts to figure out what was happening on a similar issue, I noticed that in my project there were 2 reasons that caused a retain problem and resulted in having a UIPageViewController being forever retained.
1) there was a circular reference between the UIPageViewController and the UIViewcontroller that was presented (this was fixed by changing the properties to weak from strong in both classes)
2) and the main fix consisted in changing
[self setViewControllers:#[initialDetailsViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
to
__weak __typeof__(self) weakSelf = self;
[weakSelf setViewControllers:#[initialDetailsViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
I hope this helps someone
same problem here
i resolved it by keeping my initial viewController in the variable
and instead of creating the same vc on particular pageIndex i just reuse it
I had same problem and solved the following:
[startingViewController release]; where the end point of initialization.
then the first ViewController will be deallocated.
Starting from the standard Xcode Document-based Application w/ CoreData template on 10.7, I'm experiencing some frustrating behavior. I'm sure it's something simple that I'm overlooking.
Let's say in my NSPersistentDocument subclass, I have something like this, hooked up to a button in the window:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
[self.managedObjectContext save: NULL];
}
If I create a new document and click that button, I'll get the following error: This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation. I get this. We haven't saved yet, there are no persistent stores. Makes sense.
Now let's say I split this out into two actions, hooked up to different buttons, like so:
- (IBAction)doStuff:(id)sender
{
NSEntityDescription* ent = [[self.managedObjectModel entitiesByName] valueForKey: #"MyEntity"];
NSManagedObject* foo = [[[NSManagedObject alloc] initWithEntity: ent insertIntoManagedObjectContext: self.managedObjectContext] autorelease];
}
- (IBAction)doOtherStuff:(id)sender
{
[self.managedObjectContext save: NULL];
}
If I create a new document and press the first button, then at some indeterminate time after pressing that button (dirtying the document), autosave will come along and autosave the document, which will create a store in the temp location. If I then press the second button, there are no complaints (since there's now a store.)
I need my document to be able to do managedObjectContext saves from the get-go. I'm kicking off some stuff on a background thread, and I need the background context's save operation (and notification) in order to merge the changes made by the background thread into the main thread's managedObjectContext.
I thought about trying to force an autosave, but the autosave process appears completely asynchronous, so I'd have to jump through hoops to disable any UI interaction that could cause a managedObjectContext save until the first autosave operation completes.
I also considered creating an in-memory store to bridge the gap between creating a new document and the first autosave, but then it's not clear to me how I would migrate that in memory store to the disk store and remove the memory store synchronously with the first autosave operation.
Anyone have any ideas on how I might handle this?
So I fooled around with this for a while, including trying #Aderstedt's suggestion. That approach didn't work, because faking a notification appears to merely tell the receiving context "hey, check with the persistent stores, I've updated them!", when in reality, I haven't, because there are none. I eventually found an approach that worked. Unfortunately it relies on Lion-only features, so I'm still looking for a way to do this that doesn't require Lion.
Background
I wanted to work with the NSPersistentDocument approach. Although I've not found this documented explicitly anywhere, I found several forum posts, and experienced a bunch of empirical evidence that you cannot call -[NSManagedObjectContext save:] on a context that belongs to an NSPersistentDocument. As mentioned in the question, if you call that before the document has ever been saved, it'll have no stores, so the save will fail. Even after the store exists, by saving the context directly (and not via the document save API) you are effectively changing the on-disk representation behind the NSPersistentDocument's back, and you will get the document popping sheets that say:
File has been modified by another application
In short, the NSPersistentDocument expects to control the saving action of the associated NSManagedObjectContext itself.
Also worth mentioning up front: the goal here was to make sure that the context used by the UI would trigger no (or at least minimal) I/O in order to remain responsive. The pattern I eventually settled on was to have 3 contexts. One context owned by the NSPersistentDocument, which would be responsible for doing file I/O in conjunction with the document. A second, effectively read-only, context for binding the UI to. (I realize that many folks want UI that mutates the model, so this may be less thrilling for them, but it wasn't a requirement for me.) And a third context for use on a background thread that asynchronously loads data from a web service, and hopefully pushes it into the other contexts so that it can be both saved on disk and presented in the UI without potentially blocking the UI on network I/O.
Lion-only Solution
The new parent/child NSManagedObjectContext feature in Lion's CoreData implementation is perfect for this. I replaced the NSPersistentDocument's NSManagedObjectContext with an new MOC of concurrency type NSPrivateQueueConcurrencyType. This will be the "root" context. Then I made the UI context with NSMainQueueConcurrencyType concurrency, and made it a child of the root context. Lastly I made the network-loading context a NSPrivateQueueConcurrencyType context which is a child of the UI context. The way this works is that we kick off a network load operation in the background, it updates the network context. When it's done, it saves the context. With parent/child relationships, saving a child context pushes the changes up into the parent context (the UI context) but does not save the parent context to the store. In my case, I also listen for the NSManagedObjectContextDidSaveNotification notification from the network context and then tell it's parent to save as well (which will push the changes from the UI context into the root/disk context, but will not save it to disk.)
At the end of this chain of events all the contexts are consistent, and we still haven't forced a real save of the underlying root context, and so we haven't run afoul of the NSPersistentDocument in its role of managing the on-disk representation.
One catch is that if you want to prevent saves of child contexts from generating undo (i.e. this was a network loading operation, there's nothing to undo) you have to disableUndoRegistration on each parent context as you propagate the changes up the chain.
Pre-Lion Efforts
I would really have liked to find a pre-Lion-compatible solution for this issue. I tried a few things before giving up. I first tried associating an in-memory store with the PSC on document init, so that I could do NSManagedObjectContext saves before the document saved, then migrate the in-memory store on the first save. That part worked great. But once an on-disk store existed, this approach was bogus because after it's saved to disk we have the same problem where any saving of MOCs connected to PSCs owned by an NSPersistentDocument have to be done by the document.
I also tried hacking up a mechanism to move changes from one context to another using the NSManagedObjectContextObjectsDidChangeNotification payload. Although I was able to get this to work (for some nominal definition of 'work'), I saw big problems looming on the horizon with this approach. Specifically, it's easy to migrate those changes once but what if it changes again before a save operation? Then I'd be stuck maintaining a long lived mapping of OIDs in the source context to OIDs in the destination context(s). This got ugly really fast. If anyone's interested, here's what I came up with:
#interface NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification;
#end
#implementation NSManagedObjectContext (MergeChangesFromObjectsDidChangeNotification)
- (void)mergeChangesFromObjectsDidChangeNotification: (NSNotification*)notification
{
if (![NSManagedObjectContextObjectsDidChangeNotification isEqual: notification.name])
return;
if (notification.object == self)
return;
NSManagedObjectContext* sourceContext = (NSManagedObjectContext*)notification.object;
NSAssert(self.persistentStoreCoordinator == sourceContext.persistentStoreCoordinator, #"Can't merge changes between MOCs with different persistent store coordinators.");
[sourceContext lock];
// Create object in the local context to correspond to inserted objects...
NSMutableDictionary* foreignOIDsToLocalOIDs = [NSMutableDictionary dictionary];
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSInsertedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [[[NSManagedObject alloc] initWithEntity: foreignMO.entity insertIntoManagedObjectContext: self] autorelease];
[foreignOIDsToLocalOIDs setObject: localMO.objectID forKey: foreignOID];
}
// Bring over all the attributes and relationships...
NSMutableSet* insertedOrUpdated = [NSMutableSet set];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSInsertedObjectsKey]];
[insertedOrUpdated unionSet: [[notification userInfo] objectForKey: NSUpdatedObjectsKey]];
for (NSManagedObject* foreignMO in insertedOrUpdated)
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObjectID* localOID = [foreignOIDsToLocalOIDs objectForKey: foreignOID];
localOID = localOID ? localOID : foreignOID;
NSManagedObject* localMO = [self objectWithID: localOID];
// Do the attributes.
[localMO setValuesForKeysWithDictionary: [foreignMO dictionaryWithValuesForKeys: [[foreignMO.entity attributesByName] allKeys]]];
// Do the relationships.
NSDictionary* rByName = foreignMO.entity.relationshipsByName;
for (NSString* key in [rByName allKeys])
{
NSRelationshipDescription* desc = [rByName objectForKey: key];
if (!desc.isToMany)
{
NSManagedObject* relatedForeignMO = [foreignMO valueForKey: key];
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[localMO setValue: localRelatedMO forKey: key];
}
else
{
id collection = [foreignMO valueForKey: key];
id newCollection = [NSMutableSet set];
if ([collection isKindOfClass: [NSOrderedSet class]])
{
newCollection = [NSOrderedSet orderedSet];
}
for (NSManagedObject* relatedForeignMO in collection)
{
NSManagedObjectID* relatedForeignOID = relatedForeignMO.objectID;
NSManagedObjectID* relatedLocalOID = [foreignOIDsToLocalOIDs objectForKey: relatedForeignOID];
relatedLocalOID = relatedLocalOID ? relatedLocalOID : relatedForeignOID;
NSManagedObject* localRelatedMO = [self objectWithID: relatedLocalOID];
[newCollection addObject: localRelatedMO];
}
[localMO setValue: newCollection forKey: key];
}
}
}
// And delete any objects which pre-existed in my context.
for (NSManagedObject* foreignMO in [[notification userInfo] objectForKey: NSDeletedObjectsKey])
{
NSManagedObjectID* foreignOID = foreignMO.objectID;
NSManagedObject* localMO = [self existingObjectWithID: foreignOID error: NULL];
if (localMO)
{
[self deleteObject: localMO];
}
}
[sourceContext unlock];
}
#end
Conclusion
Between the improvements in concurrency management and this parent/child feature, I quickly lost interest pursuing a pre-Lion solution. I'm beginning to gather that the pre-Lion solution would effectively be "Don't use NSPersistentDocument." As best I can tell, all these pain points go away if I drop that requirement. Without that, you can save contexts and migrate stores whenever you want, but naturally you would have to do all that work yourself.
The problem here is that the default NSManagedObjectContext that is created by the NSPersistentDocument is of concurrency type NSConfinementConcurrencyType. CoreData does not allow to create a child context of a context with that type.
As a workaround, this is working for me. The NSManagedObjectContext is created by your NSPersistentDocument, so you can override that method:
- (NSManagedObjectContext *)managedObjectContext {
if (!_context) {
NSManagedObjectContext *_default = [super managedObjectContext];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_context.persistentStoreCoordinator = _default.persistentStoreCoordinator;
}
return _context;
}
I had no idea where to get the persistentStoreCoordinator from, so I called the super implementation and get it there from. With this context you should be able to create child context from that you can use in background operations.
If you don't have a store then you can't save. It seems that you want to save the document in order to merge the changes made on a background thread; well, you can merge those changes manually. When the background thread completes, tell the main thread which objects have been updated / inserted and then make the same changes on the main thread.
If the changes are near arbitrary and thus tedious to duplicate, you can even construct your own NSManagedObjectContextDidSaveNotification on the background thread and then merge it using -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] on the main thread.
I found a very nicely written-up solution along the lines of ipmcc's solution (also running the NSPersistentDocument's MOC in a background thread, and making it the parent context of the main thread's MOC) here.
It includes the full BSD-licensed code for a document app based on that (basically a Mac OS version of iOS's UIManagedDocument).
I have an NSFetchedResultsController on the main thread. Also from the main thread, I asynchronously send out a network request for JSON. When that JSON string returns, I start an NSOperation that inits a new (background) NSManagedObjectContext, parses the JSON string, creates/updates NSManagedObject's, and saves them in the context. The background context has the same persistentStore as the main context. With this, I have 2 questions:
I thought that any saves to the persistent store from any context (on any thread) would notify the main NSFetchedResultsController that there are changes, but so far it doesn't pick up any changes. Is there something I should do to notify the main thread's NSFetchedResultsController that there were external save's so that the tableView updates accordingly?
So, on the main thread, I subscribe to NSManagedObjectContextWillSaveNotification and correctly see when all contexts (including those existing entirely on a separate thread) perform a save operation. The apple docs say that the notification.userInfo should have a dictionary of 3 arrays, one array for each of the "updated, deleted, and inserted" model objects on the background thread. However, the userInfo is always nil for me. Any ideas what I'm doing wrong?
Subscribing to the NSManagedObjectContextWillSaveNotification in AppDelegate:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextWillSaveNotification
object:nil];
And the method for when contexts are saved in AppDelegate:
- (void)managedObjectContextDidSave:(NSNotification *)notification {
DLog(#"notification: %#", notification); //not nil
DLog(#"notification user info: %#", notification.userInfo); // always nil... why??
NSManagedObjectContext *theContext = notification.object;
if(theContext != context) {
DLog(#"---- SAVED ON ANOTHER CONTEXT");
// should I notify NSFetchedResultsController that there were context saves on background threads?
// how can I merge contexts if userInfo is nil?
}
}
I'd also like to know the best practices in dealing with multiple threads (with separate NSManagedObjectContexts) and Core Data.
You observe the wrong notification: the name of the notification you need to observe is NSManagedObjectContextDidSaveNotification (not NSManagedObjectContextWillSaveNotification).
I am using MagicalRecord to help with core data operation.
I have a NSOperation subclass called OfflineRetrieveOperation. It retrieve the message from the server and save it.
the code is like this:
NSManagedObjectContext *context = [NSManagedObjectContext contextForCurrentThread];
Message *existMessage = [Message MessageWithMessageID:messageID inManagedObjectContext:context];
if (!existMessage) {
Message *message = [Message insertMessageWithProperties:properties inManagedObjectContext:context];
}
[context save];
The notification receiver is initialized like this:
- (id)init
{
self = [super init];
if (self != nil) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:[NSManagedObjectContext defaultContext]];
[self setContext:[NSManagedObjectContext context]];
}
return self;
}
the log:
-[NSManagedObjectContext(MagicalRecord) saveWithErrorHandler:](0x5906a0) Saving Context
-[NSManagedObjectContext(MagicalRecord) mergeChangesFromNotification:](0x37eab0) Merging changes to *** DEFAULT *** context *** on Main Thread ***
Everything seems work fine except I can't receive the NSManagedObjectContextDidSaveNotification at all, so that I can't know that I have already finish retrieving.
Your OfflineRetrieveOperation is probably creating its own NSManagedObjectContext. When you save that context it will fire the NSManagedObjectContextDidSaveNotification. However you have an observer that will only listen for a NSNotification being broadcast by the [NSManagedObjectContext defaultContext].
Change your observer to consume notifications from your OfflineRetrieveOperation internal NSManagedObjectContext instead of [NSManagedObjectContext defaultContext] and it should start receiving them.
So, I'm guessing you probably want to update the objects in the defaultContext when you save them in the background context. MagicalRecord actually already handles that case for you when you create a new context using the helper method. That is, when you do something like this:
NSManagedObjectContext *backgroundOperationContext =
[NSManagedObjectContext contextThatNotifiesDefaultContextOnMainThread];
The context method has already setup the notifications necessary to tell the default context to merge the changes when it saves in the background. All you need to do is keep your context alive in the background operation and call save when you're ready to persist data.
Behind the scenes, the context method is doing exactly when Marcus is suggesting, and that is, adding a notification to the notification center:
[NSNotificationCenter defaultCenter] addObserver:[NSManagedObjectContext defaultContext]
selector:...
name:NSManagedObjectContextDidSaveNotification
object:backgroundOperationContext]
This isn't exactly the code, but this is pretty much what it does.
Bottom line, forget worrying about observing and merging changes from a background context to the default context yourself, MagicalRecord takes care of it for you.
I'm analyzing an image which takes some time and meanwhile I want to display a progress indicator. For this I'm using MBProgressHUD.
It almost works... I get this error: "Modifying layer that is being finalized". I guess it's due to the fact that I do pushViewController not in my main thread. Am I right? Any ideas on how to correct this issue?
My code:
- (IBAction)buttonReadSudoku:(id)sender
{
mbProgress=[[MBProgressHUD alloc] initWithView:self.view];
mbProgress.labelText=#"Läser Sudoku";
[self.view addSubview:mbProgress];
[mbProgress setDelegate:self];
[mbProgress showWhileExecuting:#selector(readSudoku) onTarget:self withObject:nil animated:YES];
}
- (void)readSudoku
{
UIImage *image = imageView.image;
image = [ImageHelpers scaleAndRotateImage:image];
NSMutableArray *numbers = [SudokuHelpers ReadSudokuFromImage:image];
sudokuDetailViewController = [[SudokuDetailViewController alloc] init];
[sudokuDetailViewController setNumbers:numbers];
[[self navigationController] pushViewController:sudokuDetailViewController animated:YES];
}
Define a new method to push your detail view controller and use -performSelectorOnMainThread:withObject:waitUntilDone: to perform it on the main thread. Don't try to make any UI changes from other threads.
All UI changes must be in the main thread, as you note. Rather than you off-main-thread method make any changes to the UI, send an NSNotification to the current viewController telling it to do the UI work.
This is an especially good route if you're crossing an MVC border, or if you already have a viewController that knows what to do so that writing a separate method results in duplicate code.