Why this simple app with ARC leaks? - ios

So I'm relative new to objC programming. But not to C. In a more complicated app I think I have a memory leaks. I've programmed this just for make some tests. The app is very simple: it store in a MutableArray a series of integer that rappresent timers scheduled. The app has one NSTimer in the current runloop that check every second if it is the right time to ring comparing a counter with the right element of the MutableArray. Everything works, but memory in debug panel grow up, grow up, grow up…
I've try some variants but something still missing for me about ARC. I simply don't understand, since ARC is NOT a garbage collector, why memory grow and what I do wrong.
Here is the code:
-(id)initWithLabel:(UILabel *)label {
self = [super init];
self.list = [[mtAllarmList alloc]init];
self.label = label;
return self;
}
My class init function. I pass a label reference (weak beacause it is own by viewcontroller) to my class. I also allocate and init the class mtAllarmList that contain the MutableArray and other information (in the original app, file to play, volumes, eccetera).
-(void)ClockRun {
NSMethodSignature * signature = [mtClockController instanceMethodSignatureForSelector:#selector(check)];
NSInvocation * selector = [NSInvocation invocationWithMethodSignature: signature];
[selector setTarget:self];
[selector setSelector:#selector(check)];
[[NSRunLoop currentRunLoop] addTimer: self.time = [NSTimer scheduledTimerWithTimeInterval:1
invocation:selector
repeats:YES]
forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc]initWithTimeIntervalSinceNow: 30]];
}
ClockRun: is the method the app call to start everything. It simply start the timer that fires every second to check:
-(void)check {
self.counter++;
int i = [self.list check:self.counter];
if(i == 1) {
[self writeAllarmToLabel:self.label isPlayingAllarmNumber:self.counter];
}
else if (i == 2) {
[self writeAllarmToLabel:self.label theString: #"Stop"];
[self.time invalidate];
self.counter = 0;
}
else {
[self writeAllarmToLabel:self.label theString:[[NSString alloc]initWithFormat:#"controllo, %d", self.counter]];
}
NSLog(#"controllo %d", self.counter);
}
Check: simply reacts to the return value of [list check: int] methods of mtAllarmList. It returns 1 if timer must ring, 0 if not, and 2 if the sequence ends. In that case self.counter will be set to 0 and the NSTimer will be invalidate.
-(id)init {
self = [super init];
self.arrayOfAllarms = [[NSMutableArray alloc]initWithCapacity:0];
int i;
for(i=1;i<=30;++i) {
[self.arrayOfAllarms addObject: [[NSNumber alloc]initWithInt:i*1]];
}
for(NSNumber * elemento in self.arrayOfAllarms)
NSLog(#"ho creato un array con elemento %d", [elemento intValue]);
return self;
}
In mtAllarmList init method simulates the costruction an array (I've try a variety of patterns) and log all the elements.
-(int)check:(int)second {
int maxValue = [[self.arrayOfAllarms lastObject] intValue];
if(maxValue == second){
self.index = 0;
return 2;
} else {
if ([[self.arrayOfAllarms objectAtIndex:self.index] intValue] == second) {
self.index++;
return 1;
} else {
return 0;
}
}
}
Check methods instead is very elementary and I don't think needs explanations.
So, why this simple very stupid app leaks?

Since you're doing this on the main run loop, you can (and should) simplify the ClockRun method:
- (void)ClockRun {
self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(check) userInfo:nil repeats:YES];
}
That NSInvocation code was unnecessary and the NSRunLoop code could only introduce problems.
Having said that, this is unlikely to be the source of your memory consumption. And nothing else in the provided code snippets looks like an obvious memory problem. If you're 100% confident that the timer is getting invalidated, then the timer is not the problem. I wonder about the object graph between the view controller at this mtClockController. Or perhaps some circular reference in view controllers (e.g. pushing from A to B and to A again). It's hard to say on the basis of what's been provided thus far.
Sadly, there's not much else we can suggest other than the routine diagnostics. First, I'd run the the app through the static analyzer (by pressing shift+command+B in Xcode, or choosing "Profile" from the Xcode "Product" menu).
Second, you should run your app through Leaks and Allocations tools to identify the what precisely is leaking on each iteration. Do you have extra instances of the view controllers? Or just the mtClockController?
Until you identify what's not being deallocated, it's hard to remedy it. And Instruments is the best tool for identifying what's not getting released. In WWDC 2012 video iOS App Performance: Memory the demonstration sections of the video give pragmatic demonstrations of using Instruments (as well as a wealth of good background info on memory management).
Third, when I've got a situation where I'm not sure if things are getting deallocated when they should, I sometimes include dealloc methods that tell me when the object is deallocated, e.g.:
- (void)dealloc {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
I'd suggest this not only for your key model objects, but your view controller, too. (Sometimes we agonize over our model objects only to realize that it's the view controller, itself, which is be retained by something else.)
Clearly Instruments is a much richer tool, but this can be used to quickly identify failure to deallocate (and show you what's maintaining the strong references).
I ran you app through Instruments, watching your custom objects, and everything is being deallocated properly. Below, I marked generation A, hit the button, let the timer expire, marked generation B, hit the button again, etc. I did that four times, and I then simulated a memory warning, and did one final generation. Everything looks fine (this is a compilation of six screen snapshots in one, showing the total allocations at each of the six generations):
I inspected your Generations, as well as the Allocations themselves, and none of your objects are in there. Everything is getting released fine. The only things there are internal Cocoa objects associated with UIKit and NSString. Cocoa Touch does all sorts of caching of stuff behind the scenes that we have no control over. The reason I did that final "simulator memory warning" was to give Cocoa a chance to purge what it can (and you'll see that despite what Generations reports, the total allocations fell back down a bit).
Bottom line, your code is fine, and there is nothing to worry about here. In the future, don't worry about incidentally stuff showing up in the generations, but rather focus on (a) your classes; and (b) anything sizable. But neither of those apply here.
In fact, if you restrict Instruments to only record information for your classes with the mt prefix (you do this by stopping a recording of Instruments and tap on the "i" button on the Allocations graph and configure the "Recorded Types"), you'll see the sort of graph/generations that you were expecting:

A couple of observations:
Instead of using the invocation form of scheduledTimerWithInterval, try using the selector form directly, in this case it's a lot simpler and clearer to read.
Since you're call runUntilDate directly, I don't think you're getting any autorelease pools created/drained, which would lead to memory leakage, specifically in the check function. Either don't call runUntilDate and allow the normal run loop processing to handle things (the normal preferred mechanism) or wrap check in an #autoreleasepool block.

Related

Why iOs code is using so much memory and CPU

I have a some permutation/combination code that is iterating through 20 objects taking 5 at a time. When the list meets some criteria I print the objects that make up that list. Needless to say the loop is rather large. I place all of the combinations in an NSMutableArray inside of the loop. Once the objects have been added and pass/fail the test, I remove all of the object from the array. (Psuedo code below).
-(void)CreateCombinations
{
NSMutableArray *Combinations = [[NSMutableArray alloc] init];
#autoreleasepool {
NSArray *objectsList = [[NSArray alloc] initWithObjects:
#“Lisa”,#“Kevin”,…nil];
} //autorelease pool
while(!Finished)
{
Combinations = [self getNextCombo: Combinations]
if (goodCombination)
[self printCombos:Combinations]
[Combinations removeAllObject];
}
}
While monitoring the debug session, CPU and memory are at capacity. I am sure the looping is coming into play. I don't believe that I am reallocating the 'Combinations' Array for every iteration. If I am, is there something that I can do to make sure that it is properly deallocated or released before the next iteration of the loop?
When I add the #autorelease (which is before the loop) I get a "use of undeclared identifier error".
The code you've shown is completely fake so it's impossible to guess what your real code is doing wrong. But if you are using memory because you are piling up autoreleased objects during a loop, wrap the interior of the loop in an #autoreleasepool block:
while (...) {
#autoreleasepool {
// do stuff
}
}
The idea is to release the temporarily used memory every time through the loop.

Best way to return a function value and call another method after async animation block or timer has ended

I'm currently developing an iPad book-style app, where I'm using a view controller to manage the main window, and then I use a number of page controllers equal to the number of pages in the book. All the page controllers inherit from a base class, PageController, where I defined the main methods used in every page, as well as the variables.
So, my view controller tracks the current page using an object of PageController type, and when I want to load another page, the view controller calls a method (transitionToNextPage), and this method returns the next page controller.
For example, if the current page is number 2, its class is Page02Controller, and the next page class is Page03Controller, which is returned from the Page02Controller.
The issue I've been fighting with, and to which I'm asking for some advice, is when I need to call the transitionToNextPage method, and the method returns when it's still doing some actions, like animating the transition (loading some frames, for example). For example, in the code below, I call the method transitionToNextPage and I start a timer to load some frames. However, the function returns right after the timer starts, and it counts for about 1 second.
- (PageController *)transitionToNextPage{
if ([self.timerAnimationAngel isValid]) {
[self.timerAnimationAngel invalidate];
self.timerAnimationAngel = nil;
}
if ([self.timerAnimationFeather isValid]) {
[self.timerAnimationFeather invalidate];
self.timerAnimationFeather = nil;
}
[super hideTransitionButtons];
[NSTimer scheduledTimerWithTimeInterval:1.f/(float)self.filenamesImagesTransition.count target:self selector:#selector(updateTransitionViews:) userInfo:nil repeats:YES];
self.imageViewTransition = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[0]]]];
self.imageViewTransition.layer.zPosition = kZPositionTransition;
[self.mainView addSubview:self.imageViewTransition];
return [[Page02Controller alloc] initPageNumber:2 withView:self.mainView withViewController:self.mainController];
}
So far, I have all the code working as I intend, but I don't think I'm doing in the best way. What I'm doing is calling a method from the super class of page controller when the timer ends, as shown in the code below:
- (void)updateTransitionViews:(NSTimer *)timer{
static int indexImageTransition = 0;
if (indexImageTransition >= self.filenamesImagesTransition.count) {
[super clearAllViewsIncludeBackground:YES];
[timer invalidate];
timer = nil;
[super loadNextPage];
}
else{
self.imageViewTransition.image = [UIImage imageWithContentsOfFile:[NSString stringWithMainBundle:self.filenamesImagesTransition[indexImageTransition]]];
indexImageTransition++;
}
}
And the [super loadNextPage] calls a method in the view controller, and is defined in PageController (the super class) as:
- (void)loadNextPage{
SEL selector = NSSelectorFromString(#"loadNextPage");
if([self.mainController respondsToSelector:selector]){
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.mainController performSelector:selector];
#pragma clang diagnostic warning "-Warc-performSelector-leaks"
}
else{
NSLog(#"Selector not found.");
}
}
The method transitionToNextPage is called from the view controller the following way:
- (IBAction)buttonNextPage:(id)sender {
NSLog(#"Next page button pressed.");
self.currentPage = [self.currentPage transitionToNextPage];
self.pageNumber = self.currentPage.pageNumber;
}
And finally, the method loadNextPage called from the PageController is defined in the view controller as:
- (void)loadNextPage{
[self.currentPage loadPage];
}
The loadPage is now called from the next page, because it was already set before in self.currentPage = [self.currentPage transitionToNextPage];.
Here I lose the reference to the previous page, but it's not yet dealloc'ed because it still has a timer running, so I'm not having any issues doing this.
In the long term, and for future reusability, maybe the code is a bit confusing, but this way I can call the methods in the correct order, only when the timer finishes.
Another way that I was doing before was using notifications. I was listening to a notification called "#loadNextPage" which was posted by the current page controller when the timer was done. That way, I wasn't calling 3 or 4 additional methods in order to load the next page, but was only calling one.
I think the main advantage I have when using notifications is that the code is much simpler, and I just need to post/listen to a notification and call a single method.
I already thought about using a delegate, but I don't think it can be applied here the way I'm doing the transitions between pages.
I constantly run tests and measure the performance about memory and processor usage, and so far it's doing great. I don't have any memory leaks, and the app runs smoothly both on an iPad 2 with iOS 8 (non-retina) and iPad 4 with iOS 7 (retina).
EDIT:
I'm going to call the next page controller from inside the animation block or timer, without passing any information to the view controller.
I was assuming that it would retain the pages and cause leaks or have bad memory management performance, but that's not true. :)
Timers and similar hacks are simply wrong here. So is blocking or polling as a way of waiting. This is the standard asynchronous pattern. Look at any of the many Cocoa asynchronous methods to see how to deal with it, such as presentViewController:animated:completion:. Instead of writing a method transitionToNextPage, you write a method transitionToNextPageWithCompletion:. It takes a block parameter. When everything is over, the method calls the block, thus calling back into your code.

iOS ARC - Why objects not be released immediately?

Maybe this is NOT a duplicate question, as I have searched and tried many solutions about how to release objects under ARC.
The code is simple:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self recreateView];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.view addGestureRecognizer:tap];
}
- (void)tapped:(UITapGestureRecognizer *)g
{
[self recreateView];
}
- (void)recreateView
{
#autoreleasepool {
for (UIView *v in self.view.subviews) {
[v removeFromSuperview];
}
MyView *vv = [[MyView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:vv];
}
[self _performHeavyWork];
}
- (void)_performHeavyWork
{
int j = 0;
for (int i = 0 ; i < 100000000; ++i) {
j += random() % 7;
j = j % 18747;
}
}
#end
ViewController simply add a tap gesture recognizer whose action is to remove the old subview before adding a new one. MyView is a subclass of UIView which simply log a message when dealloced.
#implementation MyView
- (void)dealloc
{
NSLog(#"dealloc");
}
#end
The only magic is that the -_performHeavyWork is called every time a new view is created. When you keep on tapping on the screen quickly, the ViewController will be busy creating and discarding views. However, the odd thing is that all the discarded views are not dealloc immediately, but at the time you have stopped tapping for a while.
This is the profile of the process:
As you can see, the memory keep growing if you keep on tapping and so many of MyView instances exist at the same time. And if you comment out [self _performHeavyWork];, everything will be back to normal. So my question is:
Why do this happen?
And how can I solve it?
I think the main problem is that you're performing heavy work on the main thread. If you put the hard stuff on a different thread (or GCD) you'll probably see what you're expecting.
Here's some speculation on what's happening.
iOS responds to changes in the UI exclusively on the main thread. So if you're using the main thread for something else, the taps get queued for later processing.
You tap the screen, the main thread starts processing your heavy work.
You tap the screen some more. iOS can't deal with your request so it queues the event.
Eventually your heavy work completes and returns control to iOS.
iOS takes the queue of events and processes them all in a single run loop, which means the main loops auto release pool is never drained.
But what about the manual auto release pool? Well, all UI related stuff happens on the main loop and on the main thread, so the removeFrmSuperview: won't happen until control returns to the OS. Until that happens, the view hierarchy still holds a reference to your views, hence the memory growth.
You should never rely on dealloc being called when you think the last reference is gone. It is quite possible that references are still there where you don't expect them, but most importantly, dealloc can be called by ARC on a background thread.
First, iOS keeps a reference to a view when you add it to the visible window's view hierarchy. So, when you create a new instance your UIView subclass in the block, it remains in memory beyond the autorelease block. Second, the call to removeFromSuperview: does not actually result in the view being released by ARC until the main thread completes, which means there is still a reference to the view after the autorelease block ends. The work you're performing delays the main thread. This delays removing the final reference to the view.
Also, the autorelease block will not help in the case of removing the view because the view in question was not allocated in the same instance of the autorelease block. IOW, the view being created and added to the view hierarchy is not in the same scope when being removed later in the same block. So, there is no benefit to having the remove call in the autorelease block.

Weird bad_access with dispatch_async

I'm getting a really weird bad access error while using dispatch async. I managed to reduce it down to this segment of code in my program.
-(void)buttonTapped:(id)sender {
__block NSArray*foo = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//Foo was initially declared here but then moved it outside.
foo = [self someMethod];
[foo retain]; // bad access here. Why ?
dispatch_async(dispatch_get_main_queue(),0) {
// doesnt matter what happens here
}); });
}
-(id)someMethod
{
return [self secondMethod];
}
-(id)secondMethod
{
// was initially returning an autoreleased object from here. Changed it
// to eliminate that as source of the error.
id newThing = [[NSObject alloc] init];
return newThing;
}
The code didnt initially look like this but this is how it is right now . Including allocating a dummy NSObject .
How is it possible for foo to get released in between calls inside a dispatch async ? I dont understand how this is possible. I know its difficult to suggest whats going from just this but any debugging suggestions would be helpful. I tried turning on NSZombies but I dont get any Zombies.
You ask:
How is it possible for foo to get released in between calls inside a dispatch_async?
It shouldn't, unless someMethod or secondMethod are, themselves, doing something asynchronously which might allow the autorelease pool to be drained in the interim.
I tried turning on NSZombies but I dont get any Zombies.
If you've got zombies turned on and you're not getting a zombie, then I suspect the problem rests elsewhere. Frankly, I suspect that the root of the problem was eliminated in your process of simplifying the sample code for the purposes of the question:
A few other observations/clarifications:
You declared foo to be a NSArray, but then you're returning NSObject. I'll assume you meant it to be NSObject throughout.
You have a line of code that says:
dispatch_async(dispatch_get_main_queue(),0) {
I'll just assume that was a typo and that you intended:
dispatch_async(dispatch_get_main_queue(), ^{
The foo variable should definitely be inside the dispatch_async block. It doesn't really make sense to have a __block variable for something (a) you don't reference outside of that block for a block; and (b) for a block you're dispatching asynchronously.
The secondMethod should return an autorelease object, as you apparently originally had it. (Or you'd probably want to change secondMethod and someMethod to start with new in their names to avoid confusion and make life easier for yourself when you eventually move to ARC.)
If you retain the foo object, you'll want to also add the appropriate release. In fact, your original code sample returns a +1 object, and then retain it again, bumping it to +2, so you'd need two release calls.
Anyway, correcting for these various issues, I end up with the following, which does not generate an exception:
- (IBAction)buttonTapped:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSObject *foo = [self someMethod];
[foo retain]; // no bad access here
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"foo = %#", foo);
[foo release];
});
});
}
- (NSObject *)someMethod
{
return [self secondMethod];
}
- (NSObject *)secondMethod
{
return [[[NSObject alloc] init] autorelease];
}
Furthermore, I would suggest, especially when using manual retain and release (MRR), that you run it through the static analyzer ("Analyze" on the Xcode "Product" menu) and make sure you have a clean bill of health. (It would have pointed out some of the issues I mentioned.) It's not perfect, but it's remarkably good at identifying issues.
But, in short, the above code is fine, and if you're still getting an exception, update your question with working code that reproduces the exception.

How to force release on iOS

I'm new to ARC but understand how it works and I'm trying it out. I'm on iOS so memory is a severe concern.
I have a MyObject class which contains lots of big data. I want to release it, and load a new set of data.
MyObject *object;
object = [[MyObject alloc] initWithData:folder1]; // load data from folder1
// later...
object = [[MyObject alloc] initWithData:folder2]; // load data from folder2
This works fine without leaks, and I'm guessing the ARC inserts a [object release] before the new assignment. My problem is the data inside 'object' is released after the new set is allocated, and I run out of memory. What I really want to be able to do is:
object = nil;
<function to pop the pool, wait till everything is deallocated>
object = [MyObject alloc] initWithData:folder2]; // load data from folder2
but I'm not sure how to do that. I could run the new allocation on a performselector afterdelay, but it feels like I'm shooting in the dark and a bit of hack. There's probably a proper way to do this?
P.S I've tried searching for an answer, but all results are about memory leaks and how to make sure variables go out of scope and set variables to nil etc. My issue isn't about that, it's more of a timing thing.
UPDATE
Thanks for the answers, I'd already tried
object = nil;
object = [MyObject alloc] initWithData:folder2];
and it hadn't worked. I wasn't sure whether it was supposed to or not. Now I understand that it is supposed to work, but I must have something else holding on to it for that fraction of a second. I have NSLogs in all of my init/dealloc methods, and I can see first all the inits of the new instances of classes (of MyObject's ivars) being called, and then almost immediately after (within a few ms), the dealloc of MyObject, followed by the deallocs of its ivars.
I also tried the #autorelease but the same thing happens.
I've searched throughout the project and pasted all the code which I think may be relevant to this.
#interface AppDelegate : UIResponder <UIApplicationDelegate>;
#property PBSoundSession *soundSession;
#end
//--------------------------------------------------------------
#implementation AppDelegate
// onTimer fired at 60Hz
-(void)onTimer:(NSTimer *) theTimer {
[oscReceiver readIncoming]; // check incoming OSC messages
// then do a bunch of stuff with _soundSession;
}
#end
//--------------------------------------------------------------
#implementation OscReceiver
-(void)readIncoming {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// parse all incoming messages
if(bLoadNewSoundBank) {
NSString *newFolder = parseNewFolder();
appDelegate.soundSession = nil;
appDelegate.soundSession = [MyObject alloc] initWithData:newFolder];
}
}
#end
//--------------------------------------------------------------
#implementation GuiController
// onTimer fired at 10Hz
-(void)onTimer:(NSTimer *) theTimer {
PBSoundSession *soundSession = appDelegate.soundSession;
// update gui with received values
}
#end
I thought it might be the 'soundSession' local variable in the GuiController::onTimer holding onto the old appDelegate.soundSession for the duration of that method, but to my surprise commenting out all of the GUI code (in fact disabling the timer), made no difference.
Is there a way of finding out at that point who is still holding onto my appDelegate.soundSession? I placed a breakpoint where I set it to nil, but couldn't find any useful information. I tried Instruments in Allocation template, but couldn't find anything useful there either (probably because I don't know where to look).
This is what my allocations track looks like, you can see the memory is all deallocated a bit too late!
.
This might not be an an ARC problem. What you could be seeing is your autorelease pool not draining soon enough—your MyObject is getting released, but the data it loaded is getting held onto by the pool because of some internal -retain/-autorelease pair. Try wrapping your -initWithData: calls in an #autoreleasepool block, like this:
#autoreleasepool {
object = [[MyObject alloc] initWithData:folder1];
// do things
}
// later…
#autoreleasepool {
object = [[MyObject alloc] initWitData:folder2];
// do other things
}
Setting the object to nil immediately before setting it to something else as Gabriele suggests might cause the compiler to insert the appropriate release before the second -alloc/-initWithData:, but it might be smart enough to do that already—if that doesn’t work, it’s most likely the autorelease-pool thing.
There is no delay when draining an #autoreleasepool {...}; the objects in the pool have release invoked immediately. If an object survives that, it is because there is either a strong reference elsewhere or because the object was autoreleased into the next pool out.
If you do:
a = [[Foo alloc] initBigThing];
a = nil;
a = [[Foo alloc] initBigThing];
The first instance of Foo will be released prior to the allocation of the second
With one big caveat; if any of the code paths that a is invoked upon happen to retain/autorelease it, then it'll stick around until the pool is drained. Surrounding it in #autoreleasepool{ ... }; should do the trick.
Note that the compiler will sometimes emit retain/autorelease sequences in non-optimized builds that are eliminated in optimized builds.
A bit more general answer, I found how you can force release an object:
#import <objc/message.h>
// ---
while ([[object valueForKey:#"retainCount"] integerValue] > 1) {
objc_msgSend(object, NSSelectorFromString(#"release"));
}
objc_msgSend(object, NSSelectorFromString(#"release"));
But you shouldn't do this because ARC will probably release the object later and this will cause a crash. This method should be only used in debug!

Resources