一、实例对象、类对象、元类之间的关系
类对象在内存里只会存在一份
Person *p = [[Person alloc] init];
p.name = @"hehe";
p.age = 25;
Class cls1 = [Person class];
Class cls2 = p.class;
Class cls3 = object_getClass(p);
// cls1和cls2和cls3都是同一个类对象,类对象在内存里只会存在一份
Class cls4 = object_getClass(cls3);
// cls4 就是元类,是类对象所属的类
// 元类的所属是根元类
// 根元类的所属是自己
二、isa
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# define ISA_MASK 0x00007ffffffffff8ULL
#if SUPPORT_NONPOINTER_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
}
三、类结构
Person *p = [[Person alloc] init];
p.name = @"hehe";
p.age = 25;
Class cls1 = object_getClass(p); // 类对象
Class cls2 = object_getClass(cls1); // 元类对象
类对象:
-
第一个属性是isa // 隐藏属性 8
-
第二个属性是superclass // 8
-
第三个属性是cache // 16
-
………………
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)test
{
Class TestClass = objc_allocateClassPair([NSObject class],"TestClass",0);
// 添加属性
class_addIvar(TestClass,@"name",sizeof(id),log2(sizeof(id)),@encode(id));
objc_registerClassPair(TestClass);
id *t = [TestClass alloc];
[p setValue:@"hehe" forKey:@"name"];
NSLog(@"name == %@",[p valueForKey:@"name"]);
}
// 会报错
- (void)test
{
Class TestClass = objc_allocateClassPair([NSObject class],"TestClass",0);
objc_registerClassPair(TestClass);
// 添加属性
class_addIvar(TestClass,@"name",sizeof(id),log2(sizeof(id)),@encode(id));
id *t = [TestClass alloc];
[p setValue:@"hehe" forKey:@"name"];
NSLog(@"name == %@",[p valueForKey:@"name"]);
}
原因:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;// 为const
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
"v16@0:8"
imp (id self SEL _cmd)参数所占的总的字节数为16,下一个参数从8开始,即两个参数各占8
四、深入理解
1、方法获取
-
通过
class_getInstanceMethod从类对象中拿实例方法有
-
通过
class_getInstanceMethod从元类对象中拿实例方法无
-
通过
class_getInstanceMethod从类对象中拿类方法无
-
通过
class_getInstanceMethod从元类对象中拿类方法有
2、imp 获取
-
通过
class_getMethodImplementation从类对象中拿实例方法的IMP有
-
通过
class_getMethodImplementation从元类对象中拿实例方法的IMP有个假的 ( 消息转发函数
_objc_msgForward) -
通过
class_getMethodImplementation从类对象中拿类方法的IMP有个假的 ( 消息转发函数
_objc_msgForward) -
通过
class_getMethodImplementation从元类对象中拿类方法的IMP有
3、类方法获取
-
通过
class_getClassMethod从类对象中拿实例方法无
-
通过
class_getClassMethod从元类对象中拿实例方法无
-
通过
class_getClassMethod从类对象中拿类方法有
-
通过
class_getClassMethod从元类对象中拿类方法有
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//-----------------------
// 如果已经是个元类,就会返回类本身
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
加载类、加载分类、添加属性、方法、协议会执行attachLists
五、后续补充
该部分资料来源https://github.com/draveness/analyze (这人真是个大神,看他博客能学不少东西)
ObjC 类中的属性、方法还有遵循的协议 等信息都保存在 class_rw_t 中:
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
};
其中还有一个指向常量的指针 ro,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
在编译期间类的结构中的 class_data_bits_t *data 指向的是一个 class_ro_t * 指针
然后在加载 ObjC 运行时的时候调用 realizeClass 方法:
- 从
class_data_bits_t调用data方法,将结果从class_rw_t强制转换为class_ro_t指针 - 初始化一个
class_rw_t结构体 - 设置结构体
ro的值以及flag - 最后设置正确的
data。
const class_ro_t *ro = (const class_ro_t *)cls->data();
class_rw_t *rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
但是,在这段代码运行之后 class_rw_t 中的方法,属性以及协议列表均为空。这时需要 realizeClass 调用 methodizeClass 方法来将类自己实现的方法(包括分类)、属性和遵循的协议加载到 methods、 properties 和 protocols 列表中。
调用了 method_array_t 的 attachLists 方法,将 baseMethods 中的方法添加到 methods 数组之后。我们访问 methods 才会获取当前类的实例方法。
大佬d 总结:
- 类在内存中的位置是在编译期间决定的,在之后修改代码,也不会改变内存中的位置。
- 类的方法、属性以及协议在编译期间存放到了“错误”的位置,直到
realizeClass执行之后,才放到了class_rw_t指向的只读区域class_ro_t,这样我们即可以在运行时为class_rw_t添加方法,也不会影响类的只读结构。- 在
class_ro_t中的属性在运行期间就不能改变了,再添加方法时,会修改class_rw_t中的methods列表,而不是class_ro_t中的baseMethods,对于方法的添加会在之后的文章中分析。
我再补充一下:
ivar_list_t在class_ro_t中,而不在class_rw_t中
property_list_t在class_ro_t中,而class_rw_t中有protocol_array_t