Find where object is retained with ARC

2020-02-02 03:58发布

问题:

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?

回答1:

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.



回答2:

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.


回答3:

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<...> _delegate;
}

@synthesize delegate = _delegate;

- (void)setDelegate(id<...>)delegate {
    _delegate = delegate;  //with ARC this retains the object!
}


回答4:

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.

    + (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 %d",(int)[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 %d",(int)[self performSelector:NSSelectorFromString(@"retainCount")]);

SEL selector = NSSelectorFromString(@"myrelease");
return [self performSelector:selector withObject:nil];
#pragma clang diagnostic pop

}



回答5:

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

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

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.