向已回收的对象发送消息是不安全的。具体是否可行看具体情况,取决于对象所占用的内存是否被其他内容覆写。但这无法确认。在程序没有崩溃的情况下,那块内存可能只复用了其中一部分,对象中的某些二进制数据依然有效。还有一种可能,就是那块内存恰好被另外一个有效且存活的对象所占据。在这种情况下,运行期系统会把消息发送到新对象那里,如果新对象无法响应方法,程序依然会崩溃。
僵尸对象的工作原理,把已回收的对象转化为僵尸对象,而不彻底回收。
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface EOCClass : NSObject
@end
@implementation EOCClass
@end
void PrintClass(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);
}
以上代码中的函数,并没有直接给对象发送Objective - c的class消息,而是直接调用运行期object_getClass()函数。因为如果参数已经是僵尸对象了,直接发送OC消息会导致程序崩溃。
对象所属的类已经从EOCClass变为_NSZombie_EOCClass。_NSZombie_EOCClass实际上是在运行期产生的,当首次碰到EOCClass类的对象要变成僵尸对像时,就会创建僵尸类。僵尸类是从名为_NSZombie的模板里复制出来的。这些僵尸只是充当一个标记。
Class cls = object_getClass(self);
const char *clsName = class_getName(cls);
const char *zombieClsName = "_NSZombie_" + clsName;
Class zombieCls = objc_lookUpClass(zombieClsName);
if(!zombieCls){
Class baseZombieCls = objc_lookUpClass("_NSZombie_");
zombieCls = objc_duplicateClass(baseZombieCls, zombieClsName, 0);
}
objc_destructInstance(self);
objc_setClass(self, zombieCls);
整个过程其实就是NSObject的dealloc方法。运行期系统如果发现NSZombieEnabled的环境变量已经设置,就把dealloc方法“调配”成可以执行上述代码的方法。
僵尸类和NSObjec一样也是一个根类,并未实现任何方法,只有一个实例变量isa,所以发给它的全部消息都要经过消息转发机制。
总结:
1.系统在回收对象的时候,可以不将其真的回收,而是把它转化为僵尸对象。通过环境变量NSZombieEnabled可开启此功能。
2.系统会修改对象的isa指针,另其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够相应所有的选择子。