id obj=[NSObject alloc]init];
生成了一个obj实例对象,此对象的类型为id,编译器不做类型检查,但是其本身实际上为一个NSObject实例对象。
id 为一个typedef的一个类型,原型为 struct objc_object * ,是一个指针,指向一个objc_object类型的结构体。
所以obj为为一个指向objc_object结构体的一个指针。
打开alloc方法的定义,得到
+ (id)alloc {
return _objc_rootAlloc(self);
};
继续键入_objc_rootAlloc的定义:
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
};这里可以看到,NSObject在此处方法中是以一个Class类型被传入的,传入的是一个Class 类型的指针cls。
打开Class的定义,发现:
typedef struct objc_class *Class;说明Class也是一个指针,并且是一个指向struct objc_class的指针。
此时打开objc_class结构体的定义,得到:
struct objc_class :objc_object{
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData( class_rw_t* newData) {
bits.setData(newData);
}
...................................
};
结构体objc_class继承自结构体objc_object,第一行就注释着Class ISA;
打开objc_object结构体的定义:
struct objc_object {
private:
isa_t isa;
public:
Class ISA();
Class getIsa();
................
};
这个结构体只是一个isa_t类型的 isa变量,以及对这个isa_t结构的操作函数。
在原来的OC语言中,这个isa是一个Class类型的。在objc_object类型表示的一个实例变量中,是有这个Class类型的isa变量的,这个指针指向了这个实例所属的类。因为同一个类的不同实例有不同的数据,他们单独保存自己的数据是理所当然的,但是不同实例的方法是一样的,如果每个实例都拷贝一份类的方法,那么内存的开销是很大的,所以比较合适的方案是实例变量各自保存各自的数据,而方法应该由类保管,各个实例均采用类中保存的那一份方法储存。每个实例变量访问类中的方法的途径就是通过此Class类型的isa变量来访问对象所属的类,isa变量是类的地址。
现在OC语言将其原来的Class类型转换为了一个union数据类型的isa_t类型,现在的isa变量已经不是具有单纯的储存类的地址信息的作用了。
这里的指针变量是一个64位,占八个字节的变量,如果仅仅用来储存类的地址,某种意义上是浪费的。
所以利用union共同体,在此union中塞入从前的Class isa的指针变量(这个变量会写出来,但是不会用到,它的意义就是起到易读的作用和提示),再在其中定义一个大小为64大小的struct结构体(不同的平台,可能指针的大小不一致,所以结构体大小可能不是64位的大小),并在结构体中此利用一个少有问津的C语言技术————位域,将这个64位的结构体划分为若干个占字节数不同的变量,不同的变量所代表的不同数量字节储存不同的信息。但是如此一来如何找到类的地址信息呢?此isa变量已经不是一个指针直接指向类的地址。
我们利用实例方法class来看一看:
打开实例方法class(而非类方法class方法),此方法返回的就是一个实例的类信息:
- (Class)class{
return object_getClass(self);
};继续打开object_getClass函数的定义:
Class object_getClass(id obj)
{
if(obj)return obj->getIsa();
else return Nil;
};原来,实例方法class实际调用的是一个getIsa()函数,返回查看上方objc_object结构的部分定义时,可发现此函数的身影。
再次打开getIsa()函数的定义:
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_tslot =
(ptr >>_OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
}else{
uintptr_tslot =
(ptr >>_OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
returnobjc_tag_classes[slot];
}
};可以看到第一行调用ISA(),此函数也是定义在objc_object函数中的,并且就在getISA()上方,再次查看ISA()函数定义:
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if(isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return(Class)isa.bits;
#else
return(Class)(isa.bits&ISA_MASK);
#endif
};
可以看到,此处已经开始利用isa结构体(isa本身是个union结构,不同的平台使得isa可转化对应的struct不同),利用其中的不同位域信息来进行逻辑判断————“ if(isa.nonpointer)”,此nonpointer在结构体中占用1个字节,用来判断此isa变量是否是普通的pointer类型,如果是,则nonpointer为1,反之为0;需要注意的是,此isa变量是位于你所初始化实例变量中(实际为一个objc_object结构体,含有一个isa变量),其值是通过你实例化二步——1.alloc 2.init.两步赋值好的,用了一个上面所说的union结构体来存储了类的地址信息和其他相关信息,具体的步骤需要参考alloc方法的源码,此isa变量是否用isa_t类型来表示是alloc方法决定的,即是否将所属类的地址以特殊方式写进isa中。
判断在此起到的作用是:
一.如果使用了union类型的isa指针
1.如果此isa为特殊的isa,是附加了信息的指针,在当前的某种平台下,则取isa变量结构体的indexcls部分,毫无疑问,此变量所代表的那若干位的信息指明了所属类的地址。uintptr_t 为typedef的usigned long类型,取到的值通过classForIndex()函数处理过后返回。
2.否则是没有附加信息的指针,直接返回isa的值(实际为isa所代表的isa_t结构中唯一的成员量bits的值),此处需要将其转化为指针Class类型再返回。
二.如果没有使用union类型的isa指针
ISA_MASK为:0x00007ffffffffff8ULL
则通过和ISA_MASK掩码进行按位于操作,进行特殊位置的取值,是64位中的4到48位的信息。得到所属类的地址。
实例对象中有一个isa指针指向类对象,这个指针位于objc_object中;而objc_class结构体中也含有一个isa指针,这个指针是继承自objc_object得来的,所以可以知道,类也是一个对象,其中的isa指针指向了类对象的类,称为元类。
类对象保存了实例对象的信息——实例方法等;而元类则保存了类对象的信息——类方法。
故可知,实例对象通过isa指针找到保存在类对象当中的方法;类对象通过isa指针找到保存在元类中的类方法。
网友评论