Hen*_*bat 4 xcode memory-management nsobject nszombie swift
我创建了一个新项目,启用了僵尸对象(编辑方案 - > 诊断)。我初始化了两个对象:ZombieTest 和 ZombieTest2(从 NSObject 继承)。运行应用程序后,我打开调试内存图,只有从 NSObject 继承的对象显示为 NSZombie。
tl;dr:因为NSZombies被实现只影响NSObject及其子类。(这也与 Swift 无关:不是 Swift 的子类的 Obj-C 对象NSObject也不会变成僵尸。)
在初始化时(__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
Run Code Online (Sandbox Code Playgroud)
启用僵尸时,__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;
}
Run Code Online (Sandbox Code Playgroud)
这意味着 的所有子类NSObject及其后代,在解除分配时,将调用到__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;
}
Run Code Online (Sandbox Code Playgroud)
用更易于理解的术语来说,它:
[self class]_NSZombie_<our class name>,它会通过复制_NSZombie_该类来创建一个类,并为副本提供一个新名称(这会创建该类的副本及其所有方法实现,或缺少该类)self,并用新类替换了它的类,这样如果你将来给它发消息,你就可以分派给_NSZombie_<whatever>_NSZombie_ 是一个不实现任何方法的类,因此向它发送任何消息(方法调用)最终会落入消息转发中的代码路径,该代码路径会打印出“发送到已释放实例的消息”消息。
实际上,这种实现僵尸的方法取决于继承自NSObject(因为所有NSObject子类都应该调用[super dealloc]释放,最终到达[NSObject dealloc]);不继承自的东西NSObject不继承这个实现。(您也可以通过实现一个在其实现中不调用的NSObject子类来实际看到这一点——该对象在发布时不会被僵尸化。)[super dealloc]-dealloc
难道NSZombies 必须要实现这种方式?不,当然可以想象其他允许纯 Swift 对象参与的方案(Swift 运行时初始化也可以查找NSZombieEnabled环境变量并执行类似的操作),但是付出努力的好处要少一些。正如 Rob 在他的回答中提到的,这主要是因为我们能够调出已释放实例的类(这实际上在 Swift 运行时是可能的,但不会暴露在外部),但至关重要的是,即使我们这样做了,它对静态方法调度的情况没有帮助,这在 Swift 中的对象类型上是可能的(例如对于final类)。[亚历山大在他的评论中提到了这一点。]
因此,在很大程度上,为 Obj-C 实现这种方式真的很容易,而且花时间为纯 Swift 类执行此操作也有一些有限的好处。
| 归档时间: |
|
| 查看次数: |
336 次 |
| 最近记录: |