为什么object只有继承NSObject才会变成NSZombie?
Why does object become NSZombie only when inherit from NSObject?
我创建了一个新项目,启用了 Zombie Objects(编辑方案 -> 诊断)。
我初始化了两个对象:ZombieTest 和 ZombieTest2(继承自 NSObject)。
在 运行 应用程序之后,我打开调试内存图,只有从 NSObject 继承的对象显示为 NSZombie。
向 NSZombie 的转换由 ObjC 运行时处理,并且需要 ISA swizzle(即对象的 class 在运行时更改)。纯 Swift classes 不支持。
在某些情况下,纯 Swift 对象可能仍会成为 NSZombie,因为纯 Swift 对象有时会桥接到 ObjC 运行时,但这些是不能依赖的实现细节上。
tl;dr: 因为 NSZombies
的实现只影响 NSObject
及其子 class。 (这与 Swift 无关:不是 NSObject
的子 class 的 Obj-C 对象也不会成为僵尸。)
初始化时(__CFInitialize
,框架加载时调用),CoreFoundation 框架设置了很多低级的 Foundation 和 CoreFoundation 行为;除其他事项外,它会查找 NSZombieEnabled
环境变量,如果存在,则通过调用 __CFZombifyNSObject
函数启用僵尸程序:
; 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
启用僵尸时,__CFZombifyNSObject
将 -[NSObject dealloc]
的实现替换为不同的实现 (__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;
}
这意味着 NSObject
的所有子 class 及其后代在解除分配后将调用 __dealloc_zombie
。那么 __dealloc_zombie
是做什么的呢?
// 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;
}
用更易读的术语来说,它:
- 查找
[self class]
- 如果还没有名为
_NSZombie_<our class name>
的 class,它会通过复制 _NSZombie_
class 来创建一个,并为副本赋予一个新名称(这会创建class 的副本及其所有方法实现,或缺少)
- 它拆掉了
self
,并用新的 class 替换了它的 class,这样如果你以后给它发消息,你就会发送到 _NSZombie_<whatever>
_NSZombie_
是一个 class,它没有实现任何方法,因此向它发送任何消息(方法调用)最终都会落入消息转发中的代码路径,打印出 "message sent to deallocated instance"留言。
实际上,这种实现僵尸的方法取决于对 NSObject
的继承(因为所有 NSObject
subclasses 应该 调用 [super dealloc]
重新分配,最终达到 [NSObject dealloc]
);不继承自 NSObject
的东西不会继承此实现。 (您实际上也可以通过实现 NSObject
subclass 来看到这一点,其中 不会 在其 -dealloc
实现中调用 [super dealloc]
—该对象在发布时不会被僵尸化。)
NSZombies
有这样实施吗?不,当然可以想象其他允许纯 Swift 对象参与的方案(Swift 运行时初始化也可以查找 NSZombieEnabled
环境变量并做类似的事情),但有些付出努力的好处更少。正如 Rob 在他的回答中提到的那样,这主要是因为我们能够调出已释放实例的 class(这实际上可以通过 Swift 运行时实现,但不会暴露在外部),但至关重要的是,即使我们这样做了,它也无助于静态方法分派的情况,这在 Swift 中的对象类型上是可能的(例如 final
classes)。 [亚历山大在他的评论中提到了这一点。]
所以,在很大程度上,以这种方式为 Obj-C 实现真的很容易,而且花时间为纯 Swift classes 这样做的好处也有限。
我创建了一个新项目,启用了 Zombie Objects(编辑方案 -> 诊断)。 我初始化了两个对象:ZombieTest 和 ZombieTest2(继承自 NSObject)。 在 运行 应用程序之后,我打开调试内存图,只有从 NSObject 继承的对象显示为 NSZombie。
向 NSZombie 的转换由 ObjC 运行时处理,并且需要 ISA swizzle(即对象的 class 在运行时更改)。纯 Swift classes 不支持。
在某些情况下,纯 Swift 对象可能仍会成为 NSZombie,因为纯 Swift 对象有时会桥接到 ObjC 运行时,但这些是不能依赖的实现细节上。
tl;dr: 因为 NSZombies
的实现只影响 NSObject
及其子 class。 (这与 Swift 无关:不是 NSObject
的子 class 的 Obj-C 对象也不会成为僵尸。)
初始化时(__CFInitialize
,框架加载时调用),CoreFoundation 框架设置了很多低级的 Foundation 和 CoreFoundation 行为;除其他事项外,它会查找 NSZombieEnabled
环境变量,如果存在,则通过调用 __CFZombifyNSObject
函数启用僵尸程序:
; 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
启用僵尸时,__CFZombifyNSObject
将 -[NSObject dealloc]
的实现替换为不同的实现 (__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;
}
这意味着 NSObject
的所有子 class 及其后代在解除分配后将调用 __dealloc_zombie
。那么 __dealloc_zombie
是做什么的呢?
// 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;
}
用更易读的术语来说,它:
- 查找
[self class]
- 如果还没有名为
_NSZombie_<our class name>
的 class,它会通过复制_NSZombie_
class 来创建一个,并为副本赋予一个新名称(这会创建class 的副本及其所有方法实现,或缺少) - 它拆掉了
self
,并用新的 class 替换了它的 class,这样如果你以后给它发消息,你就会发送到_NSZombie_<whatever>
_NSZombie_
是一个 class,它没有实现任何方法,因此向它发送任何消息(方法调用)最终都会落入消息转发中的代码路径,打印出 "message sent to deallocated instance"留言。
实际上,这种实现僵尸的方法取决于对 NSObject
的继承(因为所有 NSObject
subclasses 应该 调用 [super dealloc]
重新分配,最终达到 [NSObject dealloc]
);不继承自 NSObject
的东西不会继承此实现。 (您实际上也可以通过实现 NSObject
subclass 来看到这一点,其中 不会 在其 -dealloc
实现中调用 [super dealloc]
—该对象在发布时不会被僵尸化。)
NSZombies
有这样实施吗?不,当然可以想象其他允许纯 Swift 对象参与的方案(Swift 运行时初始化也可以查找 NSZombieEnabled
环境变量并做类似的事情),但有些付出努力的好处更少。正如 Rob 在他的回答中提到的那样,这主要是因为我们能够调出已释放实例的 class(这实际上可以通过 Swift 运行时实现,但不会暴露在外部),但至关重要的是,即使我们这样做了,它也无助于静态方法分派的情况,这在 Swift 中的对象类型上是可能的(例如 final
classes)。 [亚历山大在他的评论中提到了这一点。]
所以,在很大程度上,以这种方式为 Obj-C 实现真的很容易,而且花时间为纯 Swift classes 这样做的好处也有限。