类的isa探究
- 准备研究类
LGPerson类。沿用探究oc对象的思路,使用lldb先从类的内存、isa来进行观察。
我们已经知道对象isa是指向其Class的。那Class的isa右指向哪里呢。

从结果发现LGPerson类的isa指向的是LGPerson自己。我们知道LGPerson的实例对象person的isa也是指向LGPerson的。那么两个isa的地址应该是指向了同一块内存,那么两个isa地址是否相同呢。

从图中可以到两个isa的地址是不相同。但是其两个isa都指向了LGPerson。
- 在
person对象中,其isa的指针地址& ISA_MASK后得到的是person的类LGPerson。 - 在
LGPerson类中,其isa的指针地址& ISA_MASK后得到的是LGPerson类的类。 - 在
Apple中,称LGPerson类的类为元类。
元类
对象的isa指向类,类其实也是一个对象。可称之为类对象,其isa指向apple定义的元类。元类是系统给的,其定义、创建都由编译器自动完成,类的归属来自于元类。元类是类对象的类,每个类都有一个独一无二的的元类用来存储类的相关信息。元类本身是没有名称的,由于其与类相关,所以使用了同类名相同的名称。
那么按照上面逻辑继续下去LGPerson类的类的isa指向哪里呢。

从上面看到对象的isa指向类,LGPerson 类对象的isa指向元类,都是LGPerson,最终isa最终指向了一个NSObject类。
- 那么这个
NSObject和我们内存中NSObject一样吗。 -
类对象在内存中又存在多少呢。
查看NSObject的信息

NSObject类的isa的指针地址和上面最后的根元类的isa指针地址相同。由此可见内存中NSObject和isa最终指向的NSObject应该是同一个。
创建多个类对象输出内存地址查看。
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p-\n%p", class1, class2, class3);

可以看出其地址是完全相同的,因此可以得出类对象在内存中只存在一份,那么根元类NSObject同样在内存中只存在一个份其isa指向自己。


实例对象(Instance of Subclass)的isa指向类(class)类对象(class)的isa指向元类(metal class)元类(metal class)的isa指向根元类(root metal class)根元类(root metal class)的isa指向其自己
注意:实例对象之间没有继承关系。类之间才存在继承关系。NSObject继承自nil。
类结构分析
在探究isa与cls关联的文章中使用Clang将main.m转化成c++文件探究对象的本质。同样用此去探究类Class的是什么。
typedef struct objc_class *Class;
在c++文件可以发现Class是一个objc_class的结构体。接下来进入apple提供的objc4源码中探究Class。
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;//废除
但是此定义确是已经被废除。寻找新的定义objc-runtime-new.h

内容太长了,只截了少部分objc4-781源码
在源码中搜索发现objc_object会发也有两个版本
//位于objc.h文件
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
//位于objc-private.h文件
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
....
}
结合编译的c++的main.cpp的文件中显示,使用objc.h文件中定义的版本。
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
结合objc4源码和main.cpp底层编译发现:
objc_class继承自objc_object。objc_object是结构体,拥有isa,所以
objc_class也有isa。NSObject中的isa是由Class定义,Class来自objc_class,所以NSObject同样也有isa。用
NSObject实例化一个对象objc,因继承关系objc满足objc_object的特性(拥有isa)。isa表示指向,来源于objc_object。所有的对象都是以objc_object为模板继承过来的。所以所有对象都拥有objc_object的特性。objc_object是当前的根对象。

类结构分析、属性列表、方法列表探索
@interface LGPerson : NSObject
{
NSString *oldName;
}
@property (copy, nonatomic) NSString *nickName;
-(void)sayHello;
+(void)sayNo;
@end
结构分析
struct objc_class : objc_object {
// Class ISA;//8字节
Class superclass;//8字节
cache_t cache; //16字节 // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......//方法未全部贴出
}
-
isa:虽然显示注释掉了,但是其继承自objc_object,所以是有isa的。占8字节。 -
superclass:Class类型,由objc_object定义,是一个指针占8字节。 -
cache:结构体,内存大小有其内成员决定。结构体指针占8字节。
cache内存大小计算
此处只粘贴了需要计算的部分。(用static修饰的不存在结构体内存中)
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
_buckets是struct bucket_t *类型,是结构体指针,占8字节。_mask的类型是mask_t,是unsigned int类型,占4字节。_maskAndBuckets是uintptr_t,是unsigned long,64位占8字节,32位占4字节。使用注意所处的环境。_mask_unused是mask_t类型,unsigned int类型,占4字节。_flags与_occupied是uint16_t类型,是unsigned short,占2字节。
注意:cache计算内存注意宏定义的if else并不是每一个都需要。此处使用的是64位所以其内存大小8 + 4 + 2 +2 = 16
上面我们计算好了bits前面个成员所占字节。可以通过首地址平移的方法或bits属性,查看属性及方法的存储。
bits中的数据可通过指针函数*data()获取。
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() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
......
}
bits中存储的信息类型是class_rw_t。在其源码中会发现需要的的列表


从输出的结果中可以看到LGPerson类中bits中属性列表中有nickName属性,但是却只有1一个nickName属性,没有成员变量oldName。

方法列表中有一个c++的析构函数和实例方法和属性的set和get方法。但是却没有类方法。
通过结果发现,属性、实例方式其实是存储在类当中的。
那么类方法是否存在其元类当中呢?成员变量有存储在哪里?下篇文章在探索。