36

I have an object that is being retained more than necessary (most likely due to a property that is strong instead of weak). Big codebase, so it's hard to find where.

How can I find all the lines in which this object is being retained when using ARC?

If I weren't using ARC I guess I could simply override retain and check from where it's called. Can I do something similar with ARC?

hpique
  • 119,096
  • 131
  • 338
  • 476
  • 3
    If you have a leak, why don't you use Instruments to track it down? Or the static analyzer? – David Rönnqvist Sep 04 '12 at 11:51
  • 7
    Neither instruments nor the static analyser flagged it as a leak. – hpique Sep 04 '12 at 13:37
  • 1
    @hpique If it's a retain cycle, static analyzer won't find it. Analyser will find only local leaks. If you already know the leak is there, just watch the lifecycle of your objects using Instruments. – Sulthan Sep 04 '12 at 15:40
  • 1
    Comment from @bbum : "There is a reason why -retainCount is deprecated/eliminated from ARC. It isn't generally useful and, in the specific cases that it might be useful, there is a better way. retainCount can never reflect autorelease state, nor does it reflect thread interaction. So, no, this answer is incorrect. If you want to know where objects are retained/released, use the Allocations instrument with "record reference counts". In that context, the retain count is useful because of the full fidelity of information available (the backtraces & threads that triggered it)" – David H Sep 05 '12 at 10:21

5 Answers5

43

To track growth of an application, Heapshot Analysis has proven very effective. It will capture both true leaks and accretion of memory where the allocations are not accounted for by leaks.

You can see all of the retain/release events, and their backtrace, using the Allocations instrument. Hit the little (i) button on the Allocations instrument and turn on "Record reference counts". Turning on "Only track active allocations" reduces the amount of data collected by Instruments, making it snappier (and dead allocations aren't really useful in this context, but can be in others).

With that, you can dive into any allocation (by clicking on the right-arrow in the address field), see all the retain/release events and see exactly where they occurred.

enter image description here

Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 4
    Instruments is a little different now with iOS7/Xcode5, but this is still pretty much spot on. Of course after figuring out how this all works it turned out I'd created a retain cycle in the textbook way (delegate-delegator both strongly referencing each other). *sigh* – Rembrandt Q. Einstein Oct 31 '13 at 22:10
  • 1
    @RembrandtQ.Einstein Golden words :) delegate-delegator - strong.. :) helped to me :) – Evgeniy S Feb 02 '14 at 17:24
  • @EvgeniyS : So true. This should probably be signaled by the compiler as a "are you sure ?" type of warning (much like if(a=b)). Just got bitten by it as well, even though i knew it was an error. – Ben G Mar 13 '14 at 14:02
  • For some reason, when I'm searching for the relevant object using its class, I can't find it in the Allocations instrument. Had to resort to the bottom approach for this. – rounak Apr 27 '15 at 07:50
  • 2
    Nowadays this is called "Generations" in Instruments. And you might want to profile on a *real device*, as the simulator whyever does not symbolicate the own class names. – fabb Jun 12 '15 at 06:46
  • 1
    They keep moving where the option is located. In Instruments from Xcode 10, the reference counts option is in File => Recording Options... – adam.wulf Sep 20 '18 at 23:10
  • 1
    @adam.wulf Thanks! I find myself hunting for it. – bbum Sep 21 '18 at 03:41
24

I managed to find the offending retain by doing the following:

  1. Temporarily add -fno-objc-arc to the object class Compiler Flags to disable ARC for that class.
  2. Temporarily override retain (just call super) and put a breakpoint on it.
  3. Debug and check the call stack each time retain is called.
hpique
  • 119,096
  • 131
  • 338
  • 476
  • 4
    A very effective hammer to drive a screw... :) That works -- I've used it in extreme cases -- but the Allocations Instrument with "record reference counts" enabled should have been able to show you this without any modification to your code (including a memory model change that likely introduced other bugs). – bbum Sep 05 '12 at 06:27
11

Last week I was helping some friends debug leaks in their ARC project. Some tips:

1/ Build for Profiling and start Instruments with Leak Detection. Then explore the currently allocated objects, find the object you want (you can sort them by name) and look into its retain/release history. Note that retain count is not very helpful with ARC. You have to check it manually step by step.

Try to comment all the code which could be the source of leak and then uncomment it step by step.

2/ Put a NSLog into your init and into your dealloc to watch when the object is created and destroyed.

3/ Don't look only to property definitions, watch if property setters are implemented manually. I found a problem in my friends' project looking like this:

@property (weak, nonatomic) id<...> delegate;

@interface ... {
    id<...&gt _delegate;
}

@synthesize delegate = _delegate;

- (void)setDelegate(id<...>)delegate {
    _delegate = delegate;  //with ARC this retains the object!
}
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    I'd start with using a combination of Heapshot analysis and turning on recording of reference account events using the Allocations Instrument. Nothing wrong with your answer, just that it is likely not needed in light of the capabilities of Instruments. – bbum Sep 05 '12 at 06:29
  • @bbum Allocations are turned on automatically with Leaks. – Sulthan Sep 05 '12 at 10:48
  • 1
    Yes-- leak detection is useless for all but catching the most egregious of problems. Heapshot analysis and/or tracking reference counts will catch *all* leaks and all other memory accretion, too. Just a point of clarification. – bbum Sep 05 '12 at 12:33
  • @Sulthan Why is the object retained in your property example? – Yvo Apr 15 '13 at 04:41
  • @Sulthan I don't see what's wrong with 3/. ARC should not retain the object because it is weak. For more info: http://stackoverflow.com/questions/15607404/what-is-the-correct-way-to-create-a-custom-setter-for-a-weak-property-in-objecti – Yvo Apr 15 '13 at 05:01
  • @Zyphrax Oh, sorry, forgot to add the problematic part of the code which was probably caused by migration MRC -> ARC. The problem was a _weak_ property backed up by a _strong_ ivar. – Sulthan Apr 15 '13 at 08:08
  • @Sulthan ah ok thanks, now after the edit it makes sense. I started to worry there for a minute :) – Yvo Apr 15 '13 at 09:09
  • #3 seemed to be my problem as well, except you can still override the setter and have it retain it as a weak property by using the `__weak` qualifier in the parameter list of the function eg: `- (void) setDelegate:(__weak id<#YourDelegate#>)delegate`, you may want to update your answer with this info – Fonix Nov 05 '14 at 07:37
  • (i did not have an ivar explicitly defined though, just a normal weak property) – Fonix Nov 05 '14 at 07:54
5

This solution was somewhat helpful for me. It basically uses method swizzling to tricks the ARC compiler into thinking you're not overriding retain and release.

#import <objc/runtime.h>
...
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [self class];
        
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        
        SEL originalSelector1 = NSSelectorFromString(@"retain");
        SEL swizzledSelector1 = NSSelectorFromString(@"myretain");
        
        SEL originalSelector2 = NSSelectorFromString(@"release");
        SEL swizzledSelector2 = NSSelectorFromString(@"myrelease");
        
        Method originalMethod1 = class_getInstanceMethod(cls, originalSelector1);
        Method swizzledMethod1 = class_getInstanceMethod(cls, swizzledSelector1);
        Method originalMethod2 = class_getInstanceMethod(cls, originalSelector2);
        Method swizzledMethod2 = class_getInstanceMethod(cls, swizzledSelector2);
        
        BOOL didAddMethod1 =
        class_addMethod(cls,
                        originalSelector1,
                        method_getImplementation(swizzledMethod1),
                        method_getTypeEncoding(swizzledMethod1));
    
        if (didAddMethod1) {
            class_replaceMethod(cls,
                                swizzledSelector1,
                                method_getImplementation(originalMethod1),
                                method_getTypeEncoding(originalMethod1));
        } else {
            method_exchangeImplementations(originalMethod1, swizzledMethod1);
        }
    
        BOOL didAddMethod2 =
        class_addMethod(cls,
                        originalSelector2,
                        method_getImplementation(swizzledMethod2),
                        method_getTypeEncoding(swizzledMethod2));

        if (didAddMethod2) {
            class_replaceMethod(cls,
                                swizzledSelector2,
                                method_getImplementation(originalMethod2),
                                method_getTypeEncoding(originalMethod2));
        } else {
            method_exchangeImplementations(originalMethod2, swizzledMethod2);
        }
    });
}

-(id)myretain {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    NSLog(@"tracking retain now %@",@((uintptr_t)[self performSelector:NSSelectorFromString(@"retainCount")]));
    SEL selector = NSSelectorFromString(@"myretain");
    return [self performSelector:selector withObject:nil];
#pragma clang diagnostic pop
}

-(id)myrelease {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    NSLog(@"tracking release now %@", @((uintptr_t)[self performSelector:NSSelectorFromString(@"retainCount")]));

    SEL selector = NSSelectorFromString(@"myrelease");
    return [self performSelector:selector withObject:nil];
#pragma clang diagnostic pop
}
dgatwood
  • 10,129
  • 1
  • 28
  • 49
zenchemical
  • 115
  • 1
  • 7
-3

If you are using ARC you would never get the option to add retain,

secreenshot1

and if you have converted the project to ARC using below option, you would be prompted with error

screenshot2

If you have set the property as strong, then you should allocate the object once through the whole project e.g. self.yourobject = [[NSMutableArray alloc]init];. There is no shortcut for this.

Vimal Venugopalan
  • 4,091
  • 3
  • 16
  • 25