How can I demonstrate a zombie object in Swift?
This is not a dangling pointer or a zombie. When you use !
you're saying "if this is nil, then crash." You should not think of person
as a pointer in Swift. It's a value. That value may be .some(T)
or it may be .none
(also called nil
). Neither of those is dangling. They're just two different explicit values. Swift's nil
is nothing like null pointers in other languages. It only crashes like null pointers when you explicitly ask it to.
To create zombies, you'll need to be using something like Unmanaged
. This is extremely uncommon in Swift.
How to demonstrate memory leak and zombie objects in Xcode Instruments?
For A leak:
Create two classes, A and B. A should have an @property that strongly references an instance of B. B should have an @property that strongly references an instance of A.
A *a = [A new];
B *b = [B new];
a.b = b;
b.a = a;
That'll create a leak.
For a Zombie
Create a @property that is of type assign
(or a variable of type __unsafe_unretained
. Then:
A *a = [A new];
A.dangerDanger = [NSObject new];
That should create a zombie situation; a dangling pointer, more specifically.
Enable and Debug Zombie objects in iOS using Xcode 5.1.1
Accessing a deallocated object is not the only reason you would get EXC_BAD_ACCESS. Other causes of bad access errors include accessing nil pointers and going past the bounds of an array.
Looking at your screenshots, you are not using a deallocated object. If you were using a deallocated object, the Zombies template in Instruments would let you know. Instruments would show a message similar to the following:
Your next step should be to set an exception breakpoint in Xcode. When your app crashes, Xcode will pause your app at the point where the crash occurs. To set an exception breakpoint, open the breakpoint navigator by choosing View > Navigators > Show Breakpoint Navigator. Click the + button at the bottom of the navigator and choose Add Exception Breakpoint.
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:
- Looks up
[self class]
- 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) - 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.
What is NSZombie?
It's a memory debugging aid. Specifically, when you set NSZombieEnabled
then whenever an object reaches retain count 0, rather than being deallocated it morphs itself into an NSZombie
instance. Whenever such a zombie receives a message, it logs a warning rather than crashing or behaving in an unpredictable way. As such, you can debug subtle over-release/autorelease problems without advanced tools or painstaking needle in haystack searches.
The name is a fairly obvious play on the fact that objects are normally considered "dead" when they reach retain count 0. With this setting, they continue to exist in a strange half-life - neither living, nor quite dead. Much like real zombies, except they eat rather fewer brains.
Enabling Zombie Objects causing memory pressure crashing
There's no surprise here.
Zombies are memory leaks. That is exactly how they work. The whole idea is that no objects go out existence. That is what it means to be a zombie!
Thus, if you run the app for too long with zombies on, you will leak so much memory that you crash.
can i enable zombie object on individual objects
No, that's not an option. You could perhaps try to make a highly reduced version of your app, one that produces the issue but with less memory usage, and run zombies on that.
Objective-C weak reference zombie
Is there a way to test zombies using weak references
(I take it that by "zombies" you actually mean dangling pointers...)
Not directly, no. The whole point of ARC-weak references is that they prevent dangling pointers. (As you rightly say, they safely replace the potential dangling pointer with nil — and there's no penalty for sending a message to nil.)
The reason there can be dangling pointer crashes in real life is that most of Cocoa does not use ARC. (As you rightly say, it uses unsafe_unretained
.)
Related Topics
Delay Using Dispatchtime.Now() + Float
How to Debug "Precondition Failure" in Xcode
How to Reset Intent Extension Configurations in Widgetkit
Uitableview Scrolls to Top on Reload
Swift 4: Nsfilenamespboardtype Not Available. What to Use Instead for Registerfordraggedtypes
How to Compare Just the Time of a Date in Swift
How to Tell Which Guard Statement Failed
Gradient as Foreground Color of Text in Swiftui
Scenedidload Being Called Twice
How to Detect and Make Clickable Links in a Uilabel Not Using Uitextview
Swift - Reorder Uitableview Cells
iOS 13: Threading Violation: Expected the Main Thread
Call Parent Constructor When Init a Subclass in Swift
Skaction Completion Handlers; Usage in Swift
Mutating Function Inside Class
Swift: How to Convert a String to Uint8 Array
Macos Command Line Tool with Swift Cocoa Framework: Library Not Loaded