Xcode 8 Memory Graph Says "No Selection" and Not Working

How to enable Visual Memory Debugger in Xcode 8?

It appears that Swift 3 is required for Visual Memory Debugger to work.

My app migrated to Swift 2.3 did not work, when I tried migrating it to Swift 3.0 it worked instantly.

The runtime sanitization checkbox is not required for visual memory debugger to work, however the reason that it is disabled is the same.

How to debug memory leaks when Leaks instrument does not show them?

In Xcode 8, you can click on the "Debug Memory Graph" button, debugmemorygraphbutton in the debug toolbar (shown at the bottom of the screen):

debug memory graph

See Apple’s Diagnosing and Resolving Bugs in Your Running App: Visualize and Diagnose Increasing Memory Usage.

Just identify the object in the left panel that you think should have been deallocated, and it will show you the object graph (shown in the main canvas, above). This is very useful in quickly identifying where the strong references were established on the object in question. From here, you can start your research, diagnosing why those strong references were not resolved (e.g. if the object in question has a strong reference from something else that should have been deallocated, look at that object's graph, too, and you may find the issue (e.g. strong reference cycles, repeating timers, etc.).

Notice, that in the right panel, I'm seeing the call tree. I got that by turning on the "malloc stack" logging option in the scheme settings:

malloc stack

Anyway, having done that, one can then click on the arrow next to the relevant method call shown in the stack trace in the right panel of the first screen snapshot above, and you can see where that strong reference was originally established:

code


The traditional Instruments technique (especially useful if using older versions of Xcode) is described below, in my original answer.


I would suggest using Instruments' "Allocations" tool with the "Record Reference Counts" feature:

record reference counts

You can then run the app in Instruments and then search for your class that you know is leaking and drill in by clicking on the arrow:

Sample Image

You can then drill into the details and look at the stack trace using the "Extended Details" panel on the right:

extended details

In that "Extended Details" panel, focus on your code in black rather than the system calls in gray. Anyway, from the "Extended Details" panel, you can then drill into your source code, right in Instruments::

your code

For more information and demonstrations in using Instruments to track down memory problems, please refer to:

  • WWDC 2021 video Detect and diagnose memory issues
  • WWDC 2019 video Getting Started with Instruments
  • WWDC 2018 video iOS Memory Deep Dive
  • WWDC 2013 video Fixing Memory Issues
  • WWDC 2012 video iOS App Performance: Memory

xcode memory graph not show retain cycle

Yes, Xcode does not always visually represent the strong reference cycle. But it is consistent in showing that the object in question has not yet been deallocated.

For example, if I push via a navigation controller and pop, I see the ViewController in the list of live objects, but when I select it, don’t see any cycle, but rather see some cryptic graph including the navigation controller (even though I popped it off):

Sample Image

That having been said, I find that if I present modally and dismiss the view controller in question, I will see the cycle more clearly:

Sample Image

It would be nice if we could see the prototypical cycle (like shown below), but the picture isn’t always that simple when dealing with UIKit objects.

Sample Image

Why does object become NSZombie only when inherit from NSObject?

tl;dr: because NSZombies are implemented to only affect NSObject and its subclasses. (This doesn't have to do with Swift, either: Obj-C objects which aren't subclasses of NSObject also won't become zombies.)


Upon initialization (__CFInitialize, called when the framework is loaded), the CoreFoundation framework sets up a lot low-level Foundation and CoreFoundation behaviors; among other things, it looks for the NSZombieEnabled environment variable, and if present, enables zombies by calling the __CFZombifyNSObject function:

; Disassembly from Hopper on /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation on macOS Catalina
0000000000001cc0 lea rdi, qword [aNszombieenable] ; argument #1 for method ___CFgetenv, "NSZombieEnabled"
0000000000001cc7 call ___CFgetenv ; ___CFgetenv
0000000000001ccc test rax, rax
0000000000001ccf je loc_1cee

0000000000001cd1 mov al, byte [rax] ; DATA XREF=sub_1bb8ec
0000000000001cd3 or al, 0x20
0000000000001cd5 cmp al, 0x79
0000000000001cd7 jne loc_1cee

0000000000001cd9 cmp byte [___CFZombieEnabled], 0x0 ; ___CFZombieEnabled
0000000000001ce0 jne loc_1cee

0000000000001ce2 mov byte [___CFZombieEnabled], 0xff ; ___CFZombieEnabled
0000000000001ce9 call ___CFZombifyNSObject ; ___CFZombifyNSObject

When zombies are enabled, __CFZombifyNSObject replaces the implementation of -[NSObject dealloc] with a different implementation (__dealloc_zombie):

// Hopper-generated pseudo-code:
void ___CFZombifyNSObject() {
rax = objc_lookUpClass("NSObject");
method_exchangeImplementations(class_getInstanceMethod(rax, @selector(dealloc)), class_getInstanceMethod(rax, @selector(__dealloc_zombie)));
return;
}

This means that all subclasses of NSObject and their descendants, upon deallocation, will call through to __dealloc_zombie. So what does __dealloc_zombie do?

// Hopper-generated pseudo-code:
/* @class NSObject */
-(void)__dealloc_zombie {
rbx = self;
if ((rbx & 0x1) != 0x0) goto loc_175ed5;

loc_175e3f:
if (*(int8_t *)___CFZombieEnabled == 0x0) goto loc_175eee;

loc_175e4c:
rax = object_getClass(rbx);
var_20 = 0x0;
rax = asprintf(&var_20, "_NSZombie_%s", class_getName(rax));
rax = objc_lookUpClass(var_20);
r14 = rax;
if (rax == 0x0) {
r14 = objc_duplicateClass(objc_lookUpClass("_NSZombie_"), var_20, 0x0);
}
free(var_20);
objc_destructInstance(rbx);
object_setClass(rbx, r14);
if (*(int8_t *)___CFDeallocateZombies != 0x0) {
free(rbx);
}
goto loc_175ed5;

loc_175ed5:
if (**___stack_chk_guard != **___stack_chk_guard) {
__stack_chk_fail();
}
return;

loc_175eee:
if (**___stack_chk_guard == **___stack_chk_guard) {
_objc_rootDealloc(rbx);
}
else {
__stack_chk_fail();
}
return;
}

In more human-readable terms, it:

  1. Looks up [self class]
  2. If there isn't already a class named _NSZombie_<our class name>, it creates one by duplicating the _NSZombie_ class and gives the duplicate a new name (this creates a copy of the class with all of its method implementations, or lack thereof)
  3. It tears down self, and replaces its class with the new class, so that if you message it in the future, you dispatch to _NSZombie_<whatever>

_NSZombie_ is a class which implements no methods, so sending it any message (method call) ends up falling into a code path in message forwarding which prints out the "message sent to deallocated instance" message.


Effectively, this method of implementing zombies hinges on inheritance from NSObject (because all NSObject subclasses should call [super dealloc] on deallocation, eventually reaching [NSObject dealloc]); things which don't inherit from NSObject don't inherit this implementation. (You can also actually see this by implementing an NSObject subclass which doesn't call [super dealloc] in its -dealloc implementation — the object won't get zombified on release.)

Do NSZombies have to be implemented this way? No, it's certainly possible to imagine other schemes which would allow pure Swift objects to participate (Swift runtime initialization could also look up the NSZombieEnabled environment variable and do something similar), but there's somewhat less of a benefit to putting in the effort. As Rob mentions in his answer, this works largely because we're able to swizzle out the class of the deallocated instance (this is actually possible with the Swift runtime, but not exposed externally), but crucially, even if we did this, it wouldn't help cases of static method dispatch, which is possible on object types in Swift (e.g. for final classes). [Alexander alludes to this in his comment.]

So, largely, it's really easy to implement this way for Obj-C, and there are somewhat limited benefits for taking the time to do this for pure Swift classes too.

Xcode 8 says Do you want to add a stub? How do I answer?

The answer of raphh is right but xcode is still a little buggy here (at least for me),

If you don't select the Fix-it right after compiling the dot mark transform to exclamation mark and you can't make it appear again unless you try to build one more time.

Right after building :
Dot error icon

When you don't do the Fix-it right away :
exclamation mark error icon

You have to re-build to get the dot icon again when you have several methods to implement and you did do the Fix-it for the first one.



Related Topics



Leave a reply



Submit