概述
最近在整理CoreAnimation,写代码的时候遇到了下面的问题
-[__NSArrayI rectValue]: unrecognized selector sent to instance 0x608000252060
造成unrecognized selector sent to instance XXX,大部分情况下是因为对象被提前release了,在你心里不希望他release的情况下,指针还在,对象已经不在了,网上有好多的解决办法,这里不在多说。但是这个__NSArrayI是个什么鬼?
Class Clusters
首先说一下Class Clusters(类簇)是抽象工厂模式在iOS下的一种实现,iOS中如NSString、NSArray、NSDictionary以及NSNumber都运作在这一模式下。在我们完全不知情的情况下,隐藏了很多具体的实现类,只暴露出简单的接口。
NSArray的类簇
在《effective objective-c 2.0编写高质量iOS与OS X代码的52个有效方法》中这样写道:系统框架中有许多类簇,大部分collection类都是类族。例如NSArray与其可变版本NSMutableArray。这样看来实际上有两个抽象基类,一个用于不可变数组,一个用于可变数组。尽管具备公共接口的类有两个,但任然可以合起来算一个类族。不可变的类定义了对所有数组都通用的方法,而可变类则定义了那些只适用于可变数组的方法。两个类共同属于同一个类族,这意味着二者在实现各自类型的数组时可以共用实现代码,此外还能把可变数组复制成不可变数组,反之亦然。
在使用NSArray的alloc方法来获取实例的时候,该方法会首先分配一个属于某类的实例,此实例充当“占位数组”(placeholder array)。该数组稍后会转为另一个类的实例,而那个类则是NSArray的实体子类。这个过程稍显复杂,其完整的解释已超出本书的范围。
像NSArray这样的类的背后其实是个类族(对于大部分collection类都是这样),明白这一点很重要,否则就可能写出下面这种代码:
id mybeAnArray = /**********/;
if ( [mybeAnArray class]==[NSArray class] ){
//will never be hit
}
你要是知道NSArray是个类族,那就会明白上面的代码错在哪里:其中if语句永远不可能为真。[mybeAnArray class]所返回的类绝不可能是NSArray类本身,因为由NSArray的初始化方法所返回的那个实例其类型是隐藏在类族公共接口(publlic facade)后面的某个内部类型(internal type)。
不过,任然有方法判断出某个实例所属的类是否位于类族中。如下:
id mybeAnArray = /**********/;
if( [mybeAnArray isKindOfClass:[NSArray class]]){
//will be hit
}
言归正传,下面我们来说说__NSArray0、__NSArrayI、__NSArrayM和__NSPlaceholderArray到底是什么鬼。
__NSPlacehodlerArray
为了验证,我们把将原有的alloc+init拆开写:
id arr1 = [NSArray alloc];
id arr2 = [NSMutableArray alloc];
id arr3 =[arr1 init];
id arr4=[arr2 init];
输出结果是:
arr1=__NSPlaceholderArray
arr2=__NSPlaceholderArray
arr3=__NSArray0
arr4=__NSArrayM
发现通过alloc之后都生成了__NSPlaceholderArray。后面的init都是把消息发送给了这个中间对象。再由它做工厂,生成真的对象。
所以总结一下就是:__NSArrayI是NSArray的真正类型 ,__NSArrayM是NSMutableArray真正类型。