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 want to create a basic push view scenario. I have the following code but it does not work. Nothing happens. Could someone tell me why?
testController *screen2 = [[testController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:screen2 animated:YES];
[screen2 release];
You should try to do an NSLog to see if screen2 is nil. Chances are it is not being appropriately loaded from a corresponding nib for some reason. As an aside, I'd highly recommend sticking to the convention of capitalizing class names. Speaking of which, did you maybe call the nib file TestController.nib? (That would cause the problem.)
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.
I'm using the well-known pattern to create an UIImage from an UIView:
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Now, my problem is that i have a very complex view with a lot of subviews on it, so this process of conversion takes about 3+(!!!) seconds.
I tried forking it into another thread that run in the background and it really did improve the performance.
The only problem is that as can I remember, it is not allowed to make UI related stuff not in the main thread.
Am I wrong and this is perfectly fine?
Or - if i'm right - what can be done to improve performance? is there any other method that i can use in a different thread but does the same work?
Thanks a lot!
In the end i just did it in another thread, and everything is working fine.