类对象 类 元类
举个栗子🌰
Person *p = [Person alloc];
Class p1 = [Person class];
Class p2 = [p class];
Class p3 = object_getClass(p);
NSLog(@"p1=====%p", p1);
NSLog(@"p2=====%p", p2);
NSLog(@"p3=====%p", p3);
同样从一段简单的代码切入,可以先想想这里答案是如何。没错,三个打印地址是一样的,那么为什么呢
因为这三个方法取到的都是
Person类对象,只有一份,所以地址是一样的,那么还是进入源码来看看到底是怎样
类方法class
+ (Class)class {
return self;
}
很明显就是返回类对象本身
对象方法class
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
可以看到上面例子使用的第三个方法就是类方法class的实际调用函数,同样返回的是类对象,所以它们结果是一样的。
那么此时如果再对类对象进行一次会得到什么
Class p4 = object_getClass(p1);
这次很明显就变得跟之前不一样了,那这次得到的是什么??
答案是
元类
元类
元类这个概念并不像我们之前看到的类和实例对象那样清晰可见,元类是由系统所创建的,实例对象所属的类是类对象,同样类比可以知道元类是类对象所属的类。
在源码里可以看到其中有一段
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
///省略很多
clsbits &= ISA_MASK;
return (Class)clsbits;
}
这里看到所谓isa其实是共用体,所以可以使用与运算得到一些值,这里重点看到ISA_MASK
# define ISA_MASK 0x00007ffffffffff8ULL
这里可以进行一段测试
可以对比其地址
这里由于截图截不下,第一个对象0x6000003302b0其实就是Person的实例对象,我们根据源码里的操作,每次都能得到一个新的地址,并取其isa指针,重复进行操作,最后发现一直指向同一地址,这时发现这个地址就是NSObject的元类即根源类。
类方法&对象方法
首先来看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
...
}
ISA属性是继承于objc_object的isa占8字节superclass:由objc_object定义的Class类型的指针,占有8字节 那么如果要取得bits内容显然要偏移量是前三个属性的地址后才能取得,现在来看cache_t,这个稍有复杂
cache_t
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;//8字节
explicit_atomic<mask_t> _mask;//实际为int类型,4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets;//指针,占8字节
mask_t _mask_unused;//4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
#if __LP64__
uint16_t _flags;//2字节
#endif
uint16_t _occupied;//2字节
}
首先要明确的是想要知道cache_t类型占有的内存大小不需要记录里面static修饰的属性,因为static修饰的属性并不计入结构体内存中
上方代码内已经注明各自的属性占有内存,总共三种情况,最后得到的结果均为8+4+2+2为16字节,此处不再赘述。
那么很轻松的可以得到只要偏移isa+superclass+cache即8+8+16字节的偏移后可以得到bits,下面立刻验证
可以看到这里成功取得了
class_rw_t类型的data
查看里面的方法立刻看到我们很熟悉的三个方法,分别对应方法列表,属性列表,协议列表
方法列表
通过
$8.methods()获得list_array_tt类型的一个二维数组,取得list后即可得到想要的方法列表,这里看到是得到的都是实例方法列表,如果想得到是和之前同样的方法即可
属性列表
这里是和方法列表是同样的方法来探究,因为他们的结构是一样的,都是
list_array_tt这个结构,那么这里有个问题,很明显属性列表里只有属性,那么成员变量存放到哪里了呢
在class_rw_t里看到有个class_ro_t的结构,里面有ivars属性可以试试看
通过地址偏移来验证
得到ivars的地址可以通过基于class_ro_t首地址的偏移来得到,下面进行验证
首先得到基于
class_ro_t首地址的偏移48字节,从上图可知class_ro_t的首地址是0x00000001000080b0,那么偏移48字节后地址则为0x00000001000080e0,来验证一下
最后得证,我们上面的猜想均得到了验证
那么方法列表、协议列表、属性列表是如何被添加进去的呢,直接定位到
attachLists函数
attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
整体分为三种情况,多对多,零列表,一对多,最主要的一点看到这里
这就表明了类似于后来加入的排在前面的原则。