34

Recently I was repairing someone's code. There was a big class that would not dealloc. You'd have to hit it with 5 or 6 releases to get it to dealloc.

I carefully looked through the big class and eventually found the various things that needed to be released.

This got me thinking: there just has to be some really easy way to "find" all the retains on an object .. am I right?

So, is there a simple way to "find all the retains" on an object? Is there a button in XCode or Instruments that everyone else knows about?

What do you do when you can't find a mystery retain like that?

So in the iOS universe, if anyone knows the "Show where all the retains came from on this object" button -- thanks!

P.S. Note that there is no leak, and this question is totally unrelated to leaks. The object simply "perfectly correctly" wouldn't release.


Later ..

Truly astounding solution by Fabio:

Fabio has provided an astounding solution to this problem. In nine words, here it is:

-(id)retain
    {
    NSLog(@"%@", [NSThread callStackSymbols]);
    return ([super retain]);
    }

That is amazingly useful in many situations and leads to many other useful things. You've probably saved me two man-weeks of work per annum forever, Fabio. Thanks!

BTW if you're just getting to grips with this and struggling with the output, I saw that typically there will be many chunks featuring "UINib instantiateWithOwner:". It looks like those will come first, the significant chunks will follow.

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • 2
    Of course the `CADisplayLink` retains the object. [The documentation says so](http://developer.apple.com/library/ios/#documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html): "The newly constructed display link retains the target." To get that retain to go away, you have to `invalidate` the `CADisplayLink`. – Dave DeLong Apr 07 '11 at 21:15
  • Interesting, non-trivial question (+1). The best is the first sentence ;-) – GorillaPatch Apr 07 '11 at 21:50
  • 1
    This doesn't work under ARC. You're not allowed to call [super retain]. – Greg Maletic Jul 23 '14 at 20:00
  • "In an ARC environment you will need to first add the -fno-objc-arc to compiler flags to allow you to override retain and call super" – Fattie Jun 06 '15 at 09:57

6 Answers6

72

Instruments can show you the call stack for every malloc, release, and retain for any Obj-C object in your app with no code changes required. It works when you're using ARC, which is not the case for the solution from fabio.

It's really useful for finding those mystery retains - e.g. when an object just won't dealloc when it should.

Here's how:

  • CMD + I (Product / Profile)
  • When Instruments pops up choose 'Allocations' (NOT Leaks)
  • Your app should run.
  • Do whatever causes your mystery retains to happen.
  • Select the 'Allocation' instrument on the left-hand panel.
  • Press CMD + 1 or select the circle with the wave in it on the right. In the panel on the lower right, tick the 'Record reference counts' option. This is important, or only mallocs and frees will be recorded.
  • In the search box on the top-right of the list, type the name of your class (e.g. BCMyObject).
  • This filters the list of 'Statistics' to show how many instances of your class are currently live. The #Persistent column shows how many instances are live.
  • Click the row, and then the little arrow -> next to the class name. You'll see the breadcrumbs shows 'Statistics > Allocation Summary > BCMyobject'
  • This shows you all the instances of said class (and which ones are live).
  • Select an instance, and click the arrow again (this time by address)
  • Now you'll see 'Statistics > Allocation Summary > BCMyObject > History: 0xADDRESS' in the breadcrumps.
  • This'll list every time the object is malloc'd retained or released.
  • Now in the left panel where the 'Record Reference Counts' option was, press the icon that looks like a bar with boxes connected to it or press CMD + 3.
  • Select one of the rows and you'll see the complete call stack that led to the call.

Easy! (ish)

DanBlakemore
  • 2,306
  • 2
  • 20
  • 23
Ben Clayton
  • 80,996
  • 26
  • 120
  • 129
  • 3
    Yep, that's the right answer here. Overriding "retain" was a solution as well (although a bit "hacky" maybe) some days, but it DOESN'T work for ARC. And this instruments tool - does. – Gobra Jun 02 '12 at 19:59
  • 1
    Thanks for this answer - I knew you could get this information automatically with the Leaks instrument but I couldn't work out how to get it with the allocations instrument as by default it only shows mallocs. – Luke Redpath Jun 07 '12 at 20:09
  • 1
    You sir, have saved my life and I am now eternally grateful for your contribution to the preservation of my sanity. – Jason Jun 15 '12 at 03:07
  • @BenClayton What I wonder is, why we cannot detect those with the Leaks tool instead of inspecting allocations? Is Instruments malfunctioning? Edit: I am trying to filter out instances that would not release by the end of the app. Inspection of Allocations is one option, but they don't show up as leaks in the Leaks tool. – mostruash Feb 05 '13 at 15:23
  • 1
    @mostruash there is no such thing as a leak by the end of the app. If the app closes, everything that got allocated by it gets freed. No matter if it got released before or not. – weezor Oct 09 '13 at 17:02
  • A request: could you possibly update these instructions for Xcode 6.3 and later? The Instruments UI has changed quite a bit. Much appreciated. – Greg Maletic Apr 24 '15 at 17:16
22

Just guessing... but you may overwrite the retain method of the custom class calling super and throwing a nice NSLog to print the call stack.


Update with the actual code from Joe

-(id) retain {
NSLog(@"%@", [NSThread callStackSymbols]);
return ([super retain]);
}

Another important detail is that [NSThread callStackSymbols] returns a NSArray of NSStrings that can be filtered and used for other purposes. For example in complex and dynamic code, to check if a method properly causes another one to fire.

NOTE: In an ARC environment you will need to first add the -fno-objc-arc to compiler flags to allow you to override retain and call super.

wfbarksdale
  • 7,498
  • 15
  • 65
  • 88
Fabio
  • 3,466
  • 1
  • 20
  • 15
  • I think the problem is that you want the caller of this retain, so basically one frame up in the call stack. – GorillaPatch Apr 07 '11 at 21:18
  • @Joe This should work NSLog(@"%@", [NSThread callStackSymbols]). @Gorilla I like your answer: is better because you see the stack nicely compacted by Xcode. Neat solution! – Fabio Apr 07 '11 at 22:41
  • @Joe callStackSymbols is a neat trick that I discovered in Stack Overflow (I think). Unfortunately I was not able to find the original answer. Glad that you like the solution!!! – Fabio Apr 08 '11 at 16:07
  • This doesn't work under ARC. You're not allowed to call [super retain]. – Greg Maletic Jul 23 '14 at 20:01
  • 1
    awesome Greg, thanks - I wonder what the "equivalent" is with ARC? perhaps just return super. – Fattie Jul 24 '14 at 09:21
  • you can't override retain in arc either – wfbarksdale Nov 07 '14 at 19:35
  • this solution equals zero value in today times; because everybody works with arc, and if you disable arc compilation, you will get tons of other warnings.. – Nikita Jun 11 '17 at 12:42
5

Place a breakpoint on custom class' retain

You could set a symbolic breakpoint on retain and then set it to the retain method of the custom class. The problem here is that retain is a method on NSObject so you will get the choice of all objective-c classes when placing the breakpoint.

In this case it would be better to overwrite the retain method of the custom class with a call to super, so it would not do anything but you could then place a breakpoint in it.

Use a breakpoint action to log the caller

To add a breakpoint action double click on the blue marker. Find the breakpoint in the list and press the + button on the right. Then choose Debugger command and add the GDB command frame 1 in this field, which will show you the caller of the retain. By this you cold log all retains and where they come from. When logging the releases in a similar way you could check what was the extra release.

It is still a bit tedious, but this is the best I can think of.

Community
  • 1
  • 1
GorillaPatch
  • 5,007
  • 1
  • 39
  • 56
  • Hmm. I do not know if this is a common problem. Sometimes one can do some objective-c runtime magic to find things out, but I cannot see how you can trace back a function call. – GorillaPatch Apr 07 '11 at 21:47
  • I've done this in the past when I was curious as to what was retaining an object of mine. I created a custom subclass, overrode `-retain` and just had it call `[super retain]`. If you place a breakpoint on the call to super, it's easy to look at the stack trace each time this is tripped to see who's responsible. For more frequent retain / release events, you could even use an audio breakpoint to hear these events while you use the application. – Brad Larson Apr 08 '11 at 03:09
1

Instruments and its memory management stuff is your friend. Leaks and Zombies are two of the most valuable tools available. Use them.

Product -> Profile (or Cmd-I)

Joshua Nozzi
  • 60,946
  • 14
  • 140
  • 135
  • @Joe there are plenty of resources out there for learning how to use Instruments. Start with finding the WWDC videos. As for overriding `-retain`, it'll probably work, but it feels wrong. – Dave DeLong Apr 07 '11 at 21:16
  • So, if you make this object leak, the Instruments will show all the retains of the object. – AechoLiu Apr 08 '11 at 00:38
  • 1
    The question has **absolutely no connection to leaks**, or the Leaks instrument. Cheers. – Fattie Apr 10 '11 at 08:13
1

It is, unfortunately, not easily possible to programmatically determine what "owns" an object, since the idea of "object ownership" is a coding convention (unless you enable garbage collection).

Stack logging is often useful (I usually use a few breakpoints with bt;continue) but that only tells you the function that called retain, not the "bigger picture" (e.g. you might "transfer ownership" with [ivar2 release]; ivar2 = ivar1; ivar1 = nil;). Sometimes it's a UIKit leak so you don't have the source code and you really have to go digging.

If it's not a leak, however, call -release a few times and see where it crashes!

tc.
  • 33,468
  • 5
  • 78
  • 96
-1

Have you try using "Build & Analyse" in Xcode?

It's great for getting the bottom of objects not being released.

Twelve47
  • 3,924
  • 3
  • 22
  • 29