isa, cache, bits
通过前面一篇从 MachO 加载到对象创建! 可以了解到:
- 在 alloc 的时候, 系统会开辟一片内存空间, 最终以指针的形式返回(对象).
而后便会初始化(对象的) isa ==>initIsa(cls)
, 具体可以根据源码编译跟进调试, 作为前面文章一个补充:
如果只看 Class 可跳过这一步.
编译 objc-750
首先从官方资源网下载 objc 的源码, 打开 objc.xcodeproj
文件然后进行编译 :
报错1:
The i386 architecture is deprecated.
You should update your ARCHS build setting to \
remove the i386 architecture.
(in target 'objc')
'i8386' 架构被废弃, 你应该更新你的 ARCHS 编译设置项, 删除 'i8386' 架构.
来到 target objc
的 build setting
, 搜索 ARCHS
, 会发现报错提示的 'i386' 以及 'x86_64', 删除 'i386' 再编译 :
报错2:
The i386 architecture is deprecated.
You should update your ARCHS build setting to \
remove the i386 architecture.
(in target 'objc-trampolines')
同报错1, 将 target objc-trampolines
的 build setting
下 'i386' 删除再编译 (注: 这里可以将 objc-trampolines
和 objc-simulator
两个 target 删除, 因为一般用不到) :
报错3
'sys/reason.h' file not found
这里需要将头文件 (注:需要下载多个依赖库, 其中包含想要的头文件, 比如 reason.h
在 ./xnu-4903.221.2/bsd/sys/reason.h
中, 所以需要下载该库以获取头文件) 包含到任一新建文件夹下, 并将系统头文件路径 system header search paths
设置为该文件夹目录, 具体可参照:
最新Runtime源码objc4-750编译
注: 此处并不需要依次建立文件夹, 直接放到工程目录下, 把包含的头文件路径换成头文件(即: <sys/reason.h> 修改为 <reason.h>) 也可以, 只要 system header search paths
+ import 头文件的绝对路径能找到就可以.
报错4
Use of undeclared identifier 'CRGetCrashLogMessage'
查看依赖 CrashReporterClient.h
头文件, 发现此处用了宏定义:
#ifdef LIBC_NO_LIBCRASHREPORTERCLIENT
/* Fake the CrashReporterClient API */
#define CRGetCrashLogMessage() 0
#define CRSetCrashLogMessage(x) /* nothing */
#else /* !LIBC_NO_LIBCRASHREPORTERCLIENT */
所以需要在 Build Settings
的 Preprocessor Macros
(预处理宏)类目中加入 LIBC_NO_LIBCRASHREPORTERCLIENT
环境变量来使方法生效, 继续编译:
报错5
clang:-1: linker command failed with exit code 1
这个报错很多人摸不着头脑, 因为没有报错信息, 只知道是 link 时报错, 这里有个技巧:
就是查看编译日志 (快捷键 command 9
), 这里记录详细的报错信息:
ld: can't open order file:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform
/Developer/SDKs/MacOSX10.14.sdk/AppleInternal/OrderFiles/libobjc.order
clang: error: linker command failed with exit code 1
打不开 order file
文件 (因为找不到), 别担心, 只需要换个路径就可以了, 在工程目录的 other 文件夹下同样有一份这个文件, 同样修改 Build Settings
设置, 搜索 order file
, 即可看到原来是查找 AppleInternal
目录下的文件, 替换为工程目录下的文件路径继续编译:
报错6
clang:-1: linker command failed with exit code 1
同样是 clang 错误, 这下知道怎么看详细的报错信息了, 那么:
ld: library not found for -lCrashReporterClient
clang: error: linker command failed with exit code 1
报错找不到该库文件, 由于我们前面设置过 LIBC_NO_LIBCRASHREPORTERCLIENT
环境变量, 所以这个 -lCrashReporterClient
是不需要的, 由于报错是 linker command, 所以要到 Build Settings 里面搜索 linking
, 可以看见 Other link flags 里面有设置这样的 flag (注: 也可以直接搜索 lCrashReporterClient
关键字, 搜索结果会把包含所有 lCrashReporterClient
flag 的类目找出来) , 删除该 flag 即可.
还是继续编译:
报错7
/xcodebuild:-1: SDK "macosx.internal" cannot be located.
/xcrun:-1: unable to find utility "clang++",
not a developer tool or in PATH
看着 xcodebuild
和 xcrun
报错, 又是一脸懵, 其实很简单, 还是查看编译日志:
+ /usr/bin/xcrun -sdk macosx.internal clang++ \
-Wall -mmacosx-version-min=10.12
-arch x86_64 -std=c++11
PATH
xcodebuild: error: SDK "macosx.internal" cannot be located.
xcrun: error: unable to find utility "clang++", \
not a developer tool or in PATH
xcodebuild: error: "macosx.internal" SDK 找不到, 找不到 "clang++" 命令, 明显的还有执行的命令(如下图):
原来是在执行 script 时报错, 那么就来到 build phases 中查看执行的 script 信息:
set -x
/usr/bin/xcrun -sdk macosx.internal clang++ -Wall \
-mmacosx-version-min=10.12
-arch x86_64 -std=c++11 "${SRCROOT}/markgc.cpp" -o
"${BUILT_PRODUCTS_DIR}/markgc"
"${BUILT_PRODUCTS_DIR}/markgc" "${BUILT_PRODUCTS_DIR}/libobjc.A.dylib"
脚本旨在调用 clang++
命令, 所以把 macosx.internal 改为 macosx, 使用系统自带的 clang 命令进行编译,
再编译:
报错8
no such public header file: '/tmp/objc.dst/usr/include/objc/ObjectiveC\
.apinotes'
同样, 查看编译日志:
由于是在 InstallAPI 时报错, 所以可以在 build setting 中查询关键字 InstallAPI
, 直接将 Supports Text-Based InstallAPI
设置为 NO (注: 这里也可以将 InstallAPI flags
中对应的 flag 删除以消除编译错误, 需要相继删除几个).
编译成功
到这里, 编译已经成功了, 那么接下来可以新建测试项目了:
此时应注意, 编译环境只是在 mac 下, 所以新建 Target 时只能选择 macOS 下的Application 相关项 (Cocoa App
, Game
, Command Line Tool
等), 然后添加对刚刚配置好的 objc 库依赖 (如果需要的话).
Class
有源码文件可见:
Class
定义为 typedef struct objc_class *Class;
, 是一个指向 objc_class
的结构体指针;
objc_class
包含类的 isa
, 父类, 缓存等信息:
struct objc_class : objc_object {
// Class ISA; 继承自 objc_object
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
依次:
isa
在类实现方法中 static Class realizeClass(Class cls)
, 在设置好 cls->superclass
后便会进行 isa
初始化 cls->initClassIsa(metacls)
(文章开头已给出链接, 不作赘述):
最终调用:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
对 isa
简单初始化并赋值 cls
的信息, 那么 isa
到底是什么
解析 isa
(附 superClass
):
调用如下函数(方法):
// isa 流程图验证
int isaTest() {
WXPerson *person = [[WXPerson alloc] init];
Class cls = person.class; // 类对象
Class cls4 = object_getClass(cls); // 元类
Class cls5 = object_getClass(cls4); // 根元类
NSLog(@"%@ -- %p 对象", person, person);
NSLog(@"%@ -- %p 类对象", cls,cls);
NSLog(@"%@ -- %p 元类", cls4, cls4);
NSLog(@"%@ -- %p 根元类", cls5, cls5); // 根元类
NSLog(@"%@ -- %p 对象父类", person.superclass, person.superclass);
NSLog(@"%@ -- %p 类对象父类",[cls superclass] , [cls superclass]);
NSLog(@"%@ -- %p 元类父类", [cls4 superclass], [cls4 superclass]);
NSLog(@"%@ -- %p 根元类父类", [cls5 superclass], [cls5 superclass]);
NSLog(@"%@ -- %p 根元类的isa", object_getClass(cls5),
object_getClass(cls5));
NSLog(@"%@ -- %p NSObject 父类", [[[NSObject alloc] init] superclass],
[[[NSObject alloc] init] superclass]);
NSLog(@"%@ -- %p NSObject isa", object_getClass([NSObject class]),
object_getClass([NSObject class]));
return 0;
}
这里直接贴下 log :
2019-04-03 11:36 [51997:6246754] <WXPerson: 0x100f4bd20> --0x100f4bd20 对象
2019-04-03 11:36 [51997:6246754] WXPerson -- 0x1000026d8 类对象
2019-04-03 11:36 [51997:6246754] WXPerson -- 0x1000026b0 元类
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 根元类
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002688 对象父类
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002688 类对象父类
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002660 元类父类
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b17140 根元类父类
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 根元类的isa
2019-04-03 11:36 [51997:6246754] (null) -- 0x0 NSObject 父类
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 NSObject isa
这里在贴一张根据打印地址画的一张 isa
走向图 (地址对号入座)
这里引入一个虚拟类 元类
, 元类地址可以通过 objc_getMetaClass
得到.
元类 :
通过实例对象(person
)的 class
方法可以查看对象所属类(Person
), 根据对象的 isa
可知, 类仍然是一个对象, 只是这个对象是相对于元类而言:
对象 --> 类 == 类对象 --> 元类
元类什么时候初始化 ?
运用 objc/runtime
的接口 objc_allocateClassPair
动态创建类, 通过源码可以看到, 该接口在创建类的时候, 定义了两个 Class
对象(结构体) cls
和 meta
, 然后对两个类对象作空间开辟操作:
Class objc_allocateClassPair(Class superclass,
const char *name,
size_t extraBytes)
{
Class cls, meta;
mutex_locker_t lock(runtimeLock);
// Fail if the class name is in use.
// Fail if the superclass isn't kosher.
if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}
// Allocate new classes.
// 仅仅只开辟空间, 开辟一个 objc_class 结构体大小的空间作为返回.
// _calloc_class(sizeof(objc_class) + extraBytes);
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
其次会调用到 objc_initializeClassPair_internal
函数:
static void objc_initializeClassPair_internal(Class superclass,
const char *name,
Class cls,
Class meta)
{
runtimeLock.assertLocked();
class_ro_t *cls_ro_w, *meta_ro_w;
// 开辟类/元类 data / ro
cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
cls->data()->ro = cls_ro_w;
meta->data()->ro = meta_ro_w;
// Set basic info
// 查询 RW_ FLAGS
cls->data()->flags = RW_CONSTRUCTING |
RW_COPIED_RO |
RW_REALIZED |
RW_REALIZING;
meta->data()->flags = RW_CONSTRUCTING |
RW_COPIED_RO |
RW_REALIZED |
RW_REALIZING;
cls->data()->version = 0;
meta->data()->version = 7;
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;
if (!superclass) {
cls_ro_w->flags |= RO_ROOT;
meta_ro_w->flags |= RO_ROOT;
}
if (superclass) {
cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
meta_ro_w->instanceStart =
superclass->ISA()->unalignedInstanceSize();
cls->setInstanceSize(cls_ro_w->instanceStart);
meta->setInstanceSize(meta_ro_w->instanceStart);
} else {
cls_ro_w->instanceStart = 0;
meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa
meta->setInstanceSize(meta_ro_w->instanceStart);
}
cls_ro_w->name = strdupIfMutable(name);
meta_ro_w->name = strdupIfMutable(name);
cls_ro_w->ivarLayout = &UnsetLayout;
cls_ro_w->weakIvarLayout = &UnsetLayout;
meta->chooseClassArrayIndex();
cls->chooseClassArrayIndex();
// 这里是上面 isa 及 superclass 走位图的根源!
// Connect to superclasses and metaclasses
// 初始化类对象的 isa 为 meta 类
cls->initClassIsa(meta);
if (superclass) {
// 元类的 isa 指向 元类的元类(根元类)
meta->initClassIsa(superclass->ISA()->ISA());
cls->superclass = superclass;
meta->superclass = superclass->ISA(); // 子元类的父类 是 父元类
addSubclass(superclass, cls);
addSubclass(superclass->ISA(), meta); // 父类的元类 是 子类元类的父类
} else {
meta->initClassIsa(meta); // 根元类指向本身
cls->superclass = Nil; // 根类指向 Nil
meta->superclass = cls; // 根元类指向根类
// 类关系 链表
// NSObject->nil
// 通过 mask 获取 bits.data() 将 _firstRealizedClass 设置为 cls
// cls->data()->nextSiblingClass = _firstRealizedClass;
// _firstRealizedClass = cls;
addRootClass(cls);
// NSObject(元类) nextSiblingClass -> NSObject firstSubclass
// NSObject firstSubclass -> NSObject(元类)
// 将子类的 next 指向父类 first
// cls(父类)的 firstSubclass 设置为 meta(子类)
// subcls->data()->nextSiblingClass =
// supercls->data()->firstSubclass;
// supercls->data()->firstSubclass = subcls;
// NSObject(meta) 的 父类是 NSObject, 即: 根元类的父类是 NSObject;
addSubclass(cls, meta);
}
// 初始化 cache
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
// 内部递归 meta 类, 所以不需要写 addClassTableEntry(meta);
addClassTableEntry(cls);
}
这样, 相信你应该知道元类到底是什么了:
- 元类也是
Class
; - 子元类的
isa
是 父元类 的 元类, 即: 根元类; - 根元类的
isa
指向自己; - 根类的父类是
nil
; - 子类的元类的父类是父类的元类;
- 根元类的父类是根类;
isa 作用
那么 isa 的作用是什么呢
- 查找类的实现:
在动态创建类时还需要调用另外一个接口:objc_registerClassPair(Class cls)
:
objc_registerClassPair(Class cls) {
// 改变标记值 正在创建 -> 已创建
cls->ISA()->changeInfo(RW_CONSTRUCTED, \
RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED,
RW_CONSTRUCTING | RW_REALIZING);
// Add to named class table.
addNamedClass(cls, cls->data()->ro->name);
}
该接口会将注册的 cls
以 key-value
的形式添加到 gdb_objc_realized_classes
表中 :
// 添加类实现到 hash 表中
static void addNamedClass(Class cls,
const char *name,
Class replacing = nil) {
// 插入到 NXMapTable hash 表中, 以 name 作为 key, cls 作为 value 保存
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
而 gdb_objc_realized_classes
表在 load_images
(dyld 注册的回调) -> read_images
第一次的时候就会初始化, 在查找类实现的时候会通过 name(类名) 从该表中进行查找, 即 getClass(const char *name)
:
static Class getClass(const char *name) {
runtimeLock.assertLocked();
// Try name as-is
Class result = getClass_impl(name);
if (result) return result;
}
static Class getClass_impl(const char *name)
{
runtimeLock.assertLocked();
// Try runtime-allocated table
Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
if (result) return result;
// Try table from dyld shared cache
return getPreoptimizedClass(name);
}
因为是获取类的实现, 而获取 类实现 在 objc/runtime
中, 即: objc_getClass
:
Class objc_getClass(const char *aClassName) {
return look_up_class(aClassName, NO, YES);
}
look_up_class(const char *name,
bool includeUnconnected __attribute__((unused)),
bool includeClassHandler __attribute__((unused))) {
Class result;
bool unrealized;
{
mutex_locker_t lock(runtimeLock);
result = getClass(name);
unrealized = result && !result->isRealized();
}
if (unrealized) {
mutex_locker_t lock(runtimeLock);
realizeClass(result);
}
return result;
}
可以看到, 在 look_up_class
函数中会调用 getClass
接口去获取类的实现!
- 方法查找:
在调用类方法的时候,receiver
由编译器编译成objc_getClass
(类对象) 获取类的实现(通过汇编取isa
找元类, 其实是找类对象的实现), 然后在元类中找方法的 IMP;
而调用对象方法时, 则会在通过isa
获取对象的class
(即类对象, 其实是找对象的实现) 中查找方法的IMP
;
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, isa
// optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, class_impl
, #ISA_MASK
#else
// 32-bit raw isa
mov p16, isa
#endif
.endmacro
至此, 可以了解到, IMP
的作用之一是查找类的实现(
cache
), 而且, 在调用方法的时候并没有区分类方法/对象方法, 只是通过typedef uintptr_t cache_key_t;
cache_key_t getKey(SEL sel)
{
assert(sel);
return (cache_key_t)sel;
}
去类对象中查找方法的 bits
.首先看下 cache_t 结构:
mask:缓存 bucket 的总数.
occupied:目前实际占用的缓存 bucket 的个数。
buckets:hash 表,用来缓存方法,bucket_t 类型,包含 key(sel 方法编号) 以及方法实现 IMP。
看该结构体提供方法就可以知道, cache_t 是以 hash 表的方式存储了方法的 IMP,
将 sel 方法编号转为 cache_key_t 类型即 uintptr_t 作为 key 存储.
在调用方法时, objc_msgSend 会先找 cache, 指的就是这里的方法缓存列表.
bits 结构:
可以看到 bits 中 data() 主要存储了 方法/属性/协议 列表, 并且记录当前类的第一个子类, 以及下一个类, flags 标记当前类状态. version 标记当前类类型, 如:cls version = 0, meta version = 7,
其他只读属性保存在属性 ro 结构体中.