向业已回收的对象发送消息是不安全的。这么做有时可以,有时不行。具体可行与否,完全取决于对象所占内存有没有为其他内容所覆写
开启 “僵尸对象”(Zombie Object)功能,运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正回收它们。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。“僵尸对象”是调试内存管理问题的最佳方式。
“僵尸对象”的工作原理
它的实现代码深植于Object-C的运行期程序库,Foundation框架及CoreFoundation框架中。系统在即将回收对象时,如果发现通过环境变量启用了僵尸对象功能,那么还将执行一个附加步骤。这一步就是把对象转化为僵尸对象,而不彻底回收。
void PrintClassInfo(id obj){
Class cls = object_getClass(obj);
Class superCls = class_getSuperclass(cls);
NSLog(@"=== %s : %s ===", class_getName(cls), class_getName(superCls));
}
int main(int argc, char *argv[])
{
EOCClass *obj = [[EOCClass alloc] init];
NSLog(@"Before release:");
PrintClassInfo(obj);
[obj release];
NSLog(@"After release:");
PrintClassInfo(obj);
}
/**
为了便于演示普通对象转化为僵尸对象的过程,这段代码采用了手动引用计数。因为假如使用ARC的话,str对象就会根据代码需要,尽可能多存活一段时间,于是在这个简单的例子中,就不可能变成僵尸对象了,这并是说对象在ARC下绝对不可能转化为僵尸对象。即便用了ARC,也依然会出现这种内存bug,只不过一般要通过稍微复杂些的代码才能表现出来。
*/
/**
Before release:
=== EOCClass: NSObject ===
After release:
=== _NSZombie_EOCClass:nil ===
*/
对象所属的类已由EOCClass变为_NSZombie_EOCClass。_NSZombie_EOCClass这个类是从名为NSZombie的模板类中复制出来的,且在运行期间,首次碰到EOCClass对象变成僵尸时创建的一个僵尸类,并将原对象的isa指向这个僵尸类。
// Obtain the class of the object being deallocated
Class cls = object_getClass(self);
// Get the class's name
const char *clsName = class_getName(cls);
// Prepend _NSZombie_ to the class name
const char *zombieClsName = @"_NSZombie_" + clsName;
// See if the specific zombie class exists
Class zombieCls = objc_lookUpClass(zombieClsName);
// If the specific zombie class doesn't exists,
// then it needs to be created
if(!zombieCls){
// Obtain the template zombie class, where the new class's
// name is the prepended string from above
zombieCls = objc_duplicateClass(baseZombieCls,
zombieClsName,0);
}
// Perform normal destruction of the object being deallocated
objc_destructInstance(self);
// Set the class of the object being deallocated
// to the zombie class
objc_setClass(self, zombieCls)
// The class of "self" is now _NSZombie_OriginalClass
创建僵尸类的任务由objc_duplicateClass方法来完成,它拷贝了NSZombie类结构的信息。
僵尸类的作用会在消息转发例程中体现出来。在完整的消息转发机制中,_ _ forwarding _ _ 是核心,调试程序时,大家可能在栈回溯消息里看见过这个函数。它首先要做的事情就包括检查接收消息的对象所属的类名。若名称前缀为NSZombie,则表明消息接收者是僵尸对象,需要特殊处理。此时会打印一条消息,其中指明了僵尸对象所收到的消息及原来所属的类。然后应用程序就终止了,在僵尸类名中嵌入原始类名的好处,这是就可以看出来了。只要把NSZombie从僵尸类名的开发拿掉,剩下的就是原始类名。
//伪代码演示
// Obtain the object's class
Class cls = object_getClass(self);
// Get the class's name
const char *clsName = class_getName(cls);
// Check if the class is prefixed with _NSZombie_
if(string_has_prefix(clsName,"_NSZombie_")) {
// If so, this object is a zombie
// Get the original class class name by skipping past the
// _NSZombie_,i.e. taking the substring from character 10
const char *originalClsName = substring_from(clsName, 10);
// Get the selector name of the message
const char *selectorName = sel_getName(_cmd);
// Log a message to indicate which selector is
// being sent to which zombie
Log("*** -[%s %s]: message sent to deallocated instance %p", originalClsName, selectorName, self);
// Kill the application
abort();
}
“僵尸对象”开发方式
编辑应用程序的scheme,在对话框左侧选择“Run”,然后切换至“Diagnostics”分页,最后勾选“Enable Zombie Objects”选项。
要点
系统在回收对象时,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使对象变成僵尸对象。僵尸类能够响应所有的选择子(方法),响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。