UIPageViewController memory leak - memory-leaks

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.

Related

Core Data: NSObjectID and NSTemporaryObjectID leaks

Before I send my app to the App Store I like to check it for memory leaks and other fishy stuff with instruments. There is one Core Data issue that I can't seem to solve, so I've decided to create a small test app to illustrate the problem.
What's the problem?
When I save an entity in a (child) NSManagedObjectContext it is propagated to its parent NSManagedObjectContext. During this process Core Data creates internal instances of _NSObjectID and NSTemporaryObjectID. For some reason these instances are left behind and the only way to get rid of them is to reset the parent NSManagedObjectContext.
My app is of course a lot more complex than this little test app and resetting the NSManagedObjectContext isn't an option for me.
Test app
The test app is a standard iOS app based on the single view template with the CoreData option checked. I've used objective-c to keep it similar to my production app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize the Core Data stack
self.persistentStoreCoordinator = [self persistentStoreCoordinator];
// Create the a private context
self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.rootContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
// Create a child context
self.childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.childContext.parentContext = self.rootContext;
// Create a person
[self.childContext performBlockAndWait:^{
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.childContext];
person.name = #"John Smith";
person.age = 30;
// Save the person
[self.childContext save:nil];
// Save the root context
[self.rootContext performBlockAndWait:^{
[self.rootContext save:nil];
}];
}];
return YES;
}
When you run the code above with instruments and the allocations instrument you can see that Core Data leaves some stuff behind.
You can find the full project here: https://github.com/Zyphrax/CoreDataLeak
Things I've tried
I've tried things like [context refreshObject:... mergeChanges:YES], adding #autoreleasepool and/or [context processPendingChanges] inside the blocks, it all doesn't help. The only way to get it clean is to do a [context reset] (sledgehammer approach).
It's hard to find other people reporting this problem.
This blog post seems similar:
http://finalize.com/2013/01/04/core-data-issues-with-memory-allocation/
I hope you guys can help me with this.
Here is what I see, which is very similar to yours...
However, I don't know that I would be concerned, unless you see lots of these, and they never go away. I assume the internals of Core Data (including the row cache has) some sort of object caching going on.
On the other hand, my Core Data usage has changed a bit over the past year or two.
Unless it is a very simple app, I almost never create new objects in a child context. I will fetch and modify them, but if I end up creating a new object, I make sure that is done in a sibling context.
However, if you modify your code slightly, by adding this line (with your appropriate error handling - it returns BOOL) before the initial save...
NSArray *inserted = self.childContext.insertedObjects.allObjects;
[self.childContext obtainPermanentIDsForObjects:inserted error:&error];
you should get something like this instruments report, which shows all objects created as being transient...
Thus, I don't necessarily think it is a permanent leak, because once I force the context to convert to a permanent ID, the objects go away. However, who knows how long they keep those object ID objects cached.
In general, when I create objects in a context that contains a hierarchy, I will always obtain permanent IDs first (for many reasons). However, as I said earlier, I usually create new objects in a context that is directly created to the persistent store (because I have had to deal with other issues related to hierarchies temporary object IDs, especially when using multiple non related contexts).

PushViewController not working

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.)

iOS threading "Modifying layer that is being finalized"

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.

Attempting to create USE_BLOCK_IN_FRAME ... EXC_BAD_ACCESS with NSFetchedResultsController

This is an update to my problem. I am receiving this warning now when the program aborts.
warning: Attempting to create USE_BLOCK_IN_FRAME variable with block that isn't in the frame.
I can't find much information on what this means.
This has me baffled. I get the EXC_BAD_ACCESS error. I have NSZombieEneabled (which helped with an earlier problem), but there is no call stack to trace.
I have some nearly identical code that is working with respect to another fetched result controller.
This seems to have something to do with the relationships between the job entity and its associated client entity. The relationship is [job entity] <<--> [client entity].
Initially, I see that the code works without error where the job entity corresponding to the selected row has no client entity linked through a relationship. So in the case where it fails, this points to a client entity, but when it doesn't fail, the pointer is nil.
When I encounter this problem, I start the application and go directly to the job picker view and select a cell. It's at that point that the problem occurs.
I did an experiment by starting the application and going to the client picker view first, knowing that a fetch would occur of all of the client entities. Then I went to the job picker view and selected a cell. The problem did not occur.
Since I am just trying to pass a pointer to a job entity that was already fetched, I don't understand what's happening.
By the way, the code was working fine before I switched to using NSFetchedResultsControllers. I like what they can do for me, but there are some dynamics going on here that I haven't figured out.
The logging is not showing me anything I understand toward resolving the problem.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
userState.selectedJob = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"\n\n(1 Pick) indexPath: %#\n",indexPath);
NSLog(#"\n\n(1 Pick) userState: %#\n",userState);
NSLog(#"\n\nnumber of Objects in job fetchresultscontroller = %d", [[fetchedResultsController fetchedObjects] count] );
NSLog(#"\n\n(1 Pick) selected job: %#\n",[self.fetchedResultsController objectAtIndexPath:indexPath]); // This line is causing the problem...
NSLog(#"\n\n(1 Pick) selected job: %#\n",userState.selectedJob); // Omitting the line above, this line fails
[self.navigationController pushViewController:userState.jobInfoTVC animated:YES];
}
The debug output is
2011-05-07 09:27:04.142 job1[6069:207]
(1 Pick) indexPath: <NSIndexPath 0x5952590> 2 indexes [0, 3]
2011-05-07 09:27:04.142 job1[6069:207]
(1 Pick) userState: <UserStateObject: 0x5919970>
2011-05-07 09:27:04.143 job1[6069:207]
number of Objects in job fetchresultscontroller = 4
(gdb)
The final code should be as simple as this, which led me to all of the logging:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
userState.selectedJob = [self.fetchedResultsController objectAtIndexPath:indexPath]; // Original failure was at this line
[self.navigationController pushViewController:userState.jobInfoTVC animated:YES];
}
I use the singleton userState to keep track of what the user has done. So I keep last selectedJob and selectedClient entity pointers there. This has worked okay before I switched to NSFetchedResultsController.
I have also had a problem with Attempting to create USE_BLOCK_IN_FRAME variable with block that isn't in the frame. Although mine wasn't anything to do with NSFetchedResultsControllers.
Possibly Your Problem
I noticed you mention you are using a singleton, so maybe your problem is solved at this link:
http://npenkov.com/2011/08/01/solving-issues-like-warning-attempting-to-create-use_block_in_frame-variable-with-block-that-isnt-in-the-frame/
From the link:
Exactly in session manager I used the macro, before #synthesize – this was the problem, static definitions should not appear before synthesized methods. So if you have something like:
SYNTHESIZE_SINGLETON_FOR_CLASS(SessionManager)
#synthesize loggedUserId, ...
Just replace it with:
#synthesize loggedUserId, ...
SYNTHESIZE_SINGLETON_FOR_CLASS(SessionManager)
enter code here
My problem
The problem I had was to do with duplicating a variable declaration, in this case the variable inherited from NSManagedObject :
- (void)functionThatDoesSomething {
VariableInheritedFromNSMObj *variableA = nil;
for (VariableInheritedFromNSMObj *variableA in [self containerObject]) {
NSLog(#"a = %#\n", [variableA name]);
}
[variableA setName:#"StackOverflow"];
}
Moving the first line, where variableA is initialised to nil, to after the loop fixed the problem. In my production code I then changed the name of one of the variables.
Hope that helps you or someone else who comes across this problem. This error seems to manifest itself in many different ways.
Is the deployment target OS version set to a lower one than you have debugging support for in Xcode 4.2? When this happened to me, the deployment target had somehow changed to 4.1, but I only had the 4.3 simulator without the additional debugging support for 4.0-4.1.
There are ways to fix this:
Install the debug symbols for your lower OS version from the Xcode preferences. Go to downloads, components and install OS 4.0 - 4.1 Device Debugging Support (or earlier if you need it).
Set the deployment target to 4.3 or higher.
If you choose the former, it's probably worth ticking the box in preferences to download the updates automatically.

"Core Data could not fulfill a fault.." error

I am developing an application in cocoa. I am facing a critical problem.
I am deleting entries of an object named "Directory" in Core Data using the following code:
NSEnumerator *tempDirectories = [[folderArrayController arrangedObjects] objectEnumerator];
id tempDirectory;
while (tempDirectory = [tempDirectories nextObject]){
[managedObjectContext deleteObject:tempDirectory];
}
But sometimes an exception like "Core Data could not fulfill a fault.." occurs while trying to save after deletion. I am using the code [managedObjectContext save];
I am new in Core Data... Looking forward to a solution.
This is an old question, I have struggled resolving this for some time now. So, thought it would be best to document it.
As Weichsel above mentioned, the Apple documentation rightly points out reason for this exception. But it is a hectic job to identify the module due to which the NSManagedObject subclass' object is being retained (if 1st cited reason in the documentation is the root cause of the problem).
So, I started out by identifying the parts of my code which was retaining the NSManagedObject, instead I retained the NSManagedObjectID and create the managed object out of it whenever needed. The discussion in similar lines can be found in Restkit documentation:
https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39
https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
Updated my setter and getter so that the interface with rest of the modules remain same while internally we now depend upon NSManagedObjectID and avoid retaining of NSManageObject:
-(CSTaskAbstract*)task
{
CSTaskAbstract *theTask = nil;
if (self.taskObjectID)
{
NSManagedObjectContext *moc = [(CSAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39 &
// https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
NSError *theError = nil;
NSManagedObject *theObject = [moc existingObjectWithID:self.taskObjectID
error:&theError];
if ([theObject isKindOfClass:[CSTaskAbstract class]])
{
theTask = (CSTaskAbstract*)theObject;
}
}
return theTask;
}
-(void)setTask:(CSTaskAbstract *)inTask
{
if (inTask!=self.task)
{
// Consequences of retaining a MO when it is detached from its MOC
[self setTaskObjectID:[inTask objectID]];
}
}
The above is the first half of the problem solved. We need to find out dependency in suspicious parts of our app and eliminate.
There was some other problem too, instruments -> allocations is a good source to find out which modules are actually retaining the managed objects, the exception object would have details about which managed object is creating the problem, filter results for that object as shown below:
We were performing KVO on a managed object. KVO retains the observed managed object and hence the exception is thrown and it's back trace would not be from within our project. These are very hard to debug but guess work and tracking the object's allocation and retain-release cycle will surely help. I removed the KVO observation part and it all started working.

Resources