本文完整版共三篇:
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 上)
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 中)
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 下)
3 方法
先看一下方法的结构:
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
};
简单来说,褪去华丽的外衣,方法由名字name
、 类型types
, 实现imp
。
OC 所有的方法调用其实都是发消息,通过 objc_msgSend
实现,可以通过 clang -rewrite-objc main.m -o main.cpp
将 OC 文件转换为 C++ 文件就可以证明这一点,所以重点就在 objc_msgSend
中了。
3.1 方法调用 objc_msgSend
MSG_ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p14, [x0] // p14 = raw isa
GetClassFromIsa_p16 p14, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
这就是 arm64 环境下 objc_msgSend
的源码,具体解析可以查看 # objc-msg-arm64源码深入分析。
为什么是汇编编写呢?
- 速度快
- 适用于任何参数个数,任何参数类型,任何返回值类型(C/C++, OC 都做不到)
回到 objc_msgSend
,注意看一下 LGetIsaDone
实现未在 cache 查找到方法 imp 时的实现 __objc_msgSend_uncached
,而它长什么样子呢?
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup ///+ 在方法列表中查找
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
查找 MethodTableLookup
中,再看一下源码:
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3,zai
bl _lookUpImpOrForward ///+ 调用 lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
3.2 方法查询
好了,可以看 lookUpImpOrForward
了,传入的 behavior 为 LOOKUP_INITIALIZE | LOOKUP_RESOLVER
:
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
lockdebug::assert_unlocked(&runtimeLock);
///+ 若该类还未 initilize 过,则忽略缓存
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
runtimeLock.lock();
///+ 通过 allocaltedClasses 检查该类是否已注册
checkIsKnownClass(cls);
///+ 实现该类并执行 +initalize 方法
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
lockdebug::assert_locked(&runtimeLock);
curClass = cls;
///+ 实现该类并执行 +initalize 方法
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
lockdebug::assert_locked(&runtimeLock);
curClass = cls;
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
///+ 从缓存中查找方法
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
///+ 从本来方法列表中查找 imp
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
///+ 切换为父类。若父类为空则将 imp 替换为 forward
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
///+ 若继承链中发生循环,停止执行
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
///+ 从父类缓存中查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
///+ 未找到 imp。尝试 resolve
///+ 执行 +resolveInstanceMethod 方法
if (slowpath(behavior & LOOKUP_RESOLVER)) {
///+ 去掉 LOOKUP_RESOVELER 标记
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { ///+ LOOK_NOCACHE 意味着不要缓存
#if CONFIG_USE_PREOPT_CACHES
///+ 共享缓存
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
///+ 将该方法添加至缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
#if CONFIG_USE_PREOPT_CACHES
done_unlock:
#endif
runtimeLock.unlock();
///+ 若行为标记为 LOOKUP_NIL 且未找到 IMP,则返回空,不进行 forward
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
总结流程即为:
0. 准备操作:实现该类并执行 +initilize 方法
- 沿着继承链(一直到 NSObject)分别以 cache > 方法列表的优先级查找方法
- 若未找到,则按
LOOKUP_RESOLVER
标记尝试调用 +resolveMethod 来解决且直接返回 - 找到方法后,将该方法添加至缓存并返回
- 若未找到方法,按
LOOKUP_NIL
返回 nil 或 forward
既然看了查找 imp 的方法,那就顺便看一下其他几个查找的方法:
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
lockdebug::assert_unlocked(&runtimeLock);
///+ 如果类还未 initialize,则跳转至 loopUpImpOrForward
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
///+ 从缓存中查找
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
///+ 若在缓存中未找到,则跳转至 loopUpImpOrForward
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
///+ 若行为标记为 LOOKUP_NIL 且未找到 IMP,则返回空
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior);
}
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
///+ 添加行为标记 LOOKUP_NIL(只查找真实 IMP 或 nil)
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
先看一下方法查找的过程:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
lockdebug::assert_locked(&runtimeLock);
///+ 从该类中获取方法列表
auto const methods = cls->data()->methods();
///+ 遍历每个方法列表,进行查找
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
///+ 如果方法已经被排序过(load_image 中),则二分查找
return findMethodInSortedMethodList(sel, mlist);
} else {
///+ 否则线性查找
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
return nil;
}
- 在类中查找方法时,会从
instance.isa.data.rwe
中取出所有的方法列表数组(二维数组) - 然后遍历所有的方法列表,根据该类本身在实现时的情况(每个方法是否已按照方法名排序)进行二分查找或线性查找。
3.3 动态派发
再看一下 resolveMethod_locked
:
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
lockdebug::assert_locked(&runtimeLock);
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
///+ 普通类则调用 [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
///+ 元类则先调用 [nonMetaClass resolveClassMethod:sel] ,
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
///+ 元类中未找到,则调用寻找实例方法
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
///+ 再次查找并缓存原本的 imp
///+ 这里允许找到 forward
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 如果是调用实例方法,则调用
+resolveInstanceMehtod:
尝试解决 - 如果是调用类方法,先调用
+resolveClassMehtod:
尝试解决,若未解决则调用+resolveInstanceMethod:
尝试解决。
如果该类不是元类(调用实例方法时),通过 resolveInstanceMethod
尝试 resolve
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
lockdebug::assert_unlocked(&runtimeLock);
SEL resolve_sel = @selector(resolveInstanceMethod:);
///+ 在类中查找 +resolveInstanceMethod:
///+ 将类作为实例在元类中查找:第一个参数为 cls,第三个参数为元类
///+ 因为 +resolveInstanceMethod: 是类方法
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
///+ 未实现 +resolveInstanceMethod:
///+ 一般不会走到这里,NSObject 中有默认实现
return;
}
///+ 若类实现了 +resolveInstanceMethod: ,则将原始 sel 作为参数调用
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
///+ 再次查找并缓存原本的 imp
///+ 这里仅查找真实 IMP 或 nil
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
}
- 首先确保类是否实现在
+resolveInstanceMethod:
方法,如果未实现则直接 return(但 NSObject 中会有默认实现) - 调用
+resolveInstanceMethod:
(可以再该方法内动态添加一个 IMP) - 再次通过原本的 sel 查找 IMP 并缓存(如果在第二步中添加了对应的 IMP 则可以找到)
若该类是元类(调用类方法),通过 resolveClassMethod
> resolveInstanceMethod
的优先级尝试 resolve:
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
lockdebug::assert_unlocked(&runtimeLock);
///+ 首先尝试在元类中查找缓存 resolveClassMethod: 方法的结果
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
///+ 未实现 +resolveClassMethod:
///+ 但 NSObject 中有默认实现
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
///+ 获取原始的类对象,因为 +resolveClassMethod: 消息是要发送给类对象的
///+ 里边有个特殊情况,对于根类来说,他的元类就是自身,比如 NSObject
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal(...);
}
}
///+ 调用 +resolveClassMethod:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
///+ 再次查找并缓存原本的 imp
///+ 这里仅查找真实 IMP 或 nil
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
...
打印
...
}
大致逻辑与 +resolveInstanceMethod:
相同,有一点比较重要。这里额外做了一个获取原始类对象的操作,因为传入的 cls 可能是类对象,也可能是元类对象,而 +resolveClassMethod:
这个消息是要发送给类对象,而不是元类对象。(只不过最终是在元类中查找该方法,但消息还是发送给原始类对象的)
从 resolveMethod_nolock
中 cls 是元类来看,调用一个类的类方法时,若该类方法未实现且没有实现 +resolveClassMethod:
,最终会调用到根类的同名实例方法。
为什么是根类呢?因为根类的元类就是其自身。验证一下:
@interface SomeObject : NSObject
+ (void)sameMethod;
@end
@@implementation SomeObject
@end
@interface NSObject (Cat)
@end
@@implementation NSObject (Cat)
- (void)sameMethod {
NSLog(@"执行了实例方法:%s", __FUNCTION__);
}
@end
尝试调用一下 [SomeObject sameMethod]
看下结果。
当然,除了 objc_msgSend
外,还有其他好几个类似的方法,比较特殊且重要的就是 objc_msgSendSuper
。先对比下两者的公开定义:
id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
很明显,除了方法名之外,第一个参数(也就是消息接收者)不同。看一眼 objc_super
是什么:
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
///+ 消息接收者
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
///+ 指定接收消息的特定父类,是继承链的直接父类
__unsafe_unretained _Nonnull Class super_class;
};
一个误区
为一个什么都没有的类添加一个 init
方法:
@implementation SomeObject
- (instancetype)init {
// self = [super init]; // 为了让代码更少,这行暂时不要
NSStringFromClass([super class]));
return self;
}
@end
使用 clang -rewrite-objc SomeObject.m -o SomeObject.cpp
重写成 C++,只看 找到该方法的定义:
static instancetype _I_SomeObject_init(SomeObject * self, SEL _cmd) {
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SomeObject"))}, sel_registerName("class"));
return self;
}
认真看一下 objc_msgSenSuper
的第一个参数是什么?
(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SomeObject"))}
改写一下:
(struct objc_super){
self,
SomeObject.superclass
}
此时,消息接受者其实依然是 self
,也就是调用处的这个对象自身。只不过第一消息响应类不是 SomeObject。
所以,这里在查找方法时,完全可以改写成刚才我们看过的 lookUpImpOrForward
格式
lookUpImpOrForward(self, @selector(class), SomeObject.superclass, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
此时接收消息的依然是 self。所以这里拿到的依然是 SomeObject,而不是它的父类。
3.4 消息转发
在 3.1 方法调用 查找方法时,有一个 _objc_msgForward_impcache
。在未找到对应 sel 的方法且 behavior 未标记为 LOOKUP_NIL 时的返回。
从字面意思看,消息转发。还在是 objc-msg-arm64.s 中,依然是汇编编写:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
///+ 无结构体特殊版本
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
///+ 继续到这里
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
好了,可以去找 __objc_forward_handler
了:
__attribute__ ((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
}
先看一下 objc_defaultForwarHandler
方法内部实现。unrecognized selector sent to instance,嗯嗯这个提示很熟悉,调用未实现的方法就是这报错。
再看一眼下边的 objc_setForwarHandler
,跟 uncaught_handler
一样,我们只要设置好这个 handler 就可以愉快地转发消息了。
BUT~当我设置之后,并打印不出来任何堆栈信息。看来 Apple 搞了幺蛾子
既然这里搞不定,那就回到 Apple 公开的手段上:
- (id)forwardingTargetForSelector:(SEL)aSelector;
+ (id)forwardingTargetForSelector:(SEL)aSelector;
一者为实例方法,二者为类方法,都是为了找一个“替死鬼”来处理这个消息,例如这样:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector("unimplementedMethod") {
return otherObject;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector("unimplementedMethod") {
return OtherClass;
}
return [super forwardingTargetForSelector:aSelector];
}
如果这里没有“替死鬼”返回了 nil,还有最后一次机会:runtime 会向对象 -methodSignatureForSelector:
消息,先拿到方法的完整签名生成 NSMethodInvocation
,然后将其联合本次方法调用(消息发送)的参数包装成一个 NSInvocation
,然后再向对象发送 forwardInvocation:
消息,我们就可以处理这个 invocation 了:
- (void)forwardInvocation:(NSInvocation *)invocation;
+ (void)forwardInvocation:(NSInvocation *)invocation;
依然是实例方法与类方法共存:
- (void)forwardInvocation:(NSInvocation *)invocation
if ([invocation selector] == @selector(unimplementedMethod)) {
[invocation invokeWithTarget:otherObject];
return;
}
return [super forwardInvocation:invocation];
}
+ (void)forwardInvocation:(NSInvocation *)invocation
if ([invocation selector] == @selector(unimplementedMethod)) {
[invocation invokeWithTarget:otherClass];
return;
}
return [super forwardInvocation:invocation];
}
好了总结一下:
- objc_msgSend
- NilOrTagged
- CacheLookup
- MethodTableLookup
- lookUpImpOrForward /// 找到 IMP 则调用
- resolveMethod,回调到 5
- forwardingTargetForSelector /// 找到“替死鬼”就让它处理
- methodSignatureForSelector
- forwardInvocation /// 让下一个“替死鬼”处理,没找到就崩溃
或者看一下大佬的图
4 其他
4.1 class_rw_t
与 class_ro_t
首先从名字上就能看出来,class_rw_t
是可读可写的,class_ro_t
是只读的。
先看一下 class_ro_t
:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart; ///+ 实例其实地址
uint32_t instanceSize; ///+ 实例占用空间大小
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout; ///+ 实例变量布局。这表示成员变量在对象内存空间内占用的内存块
Class nonMetaclass; ///+ 非元类
};
explicit_atomic<const char *> name; ///+ 类名
WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods; ///+ 编译期间就确定的方法(不包括分类中的)
protocol_list_t * baseProtocols; ///+ 编译期间就确定的协议
const ivar_list_t * ivars; ///+ 成员变量列表
const uint8_t * weakIvarLayout; ///+ 弱引用成员变量布局。这表示弱引用成员变量在对象内存空间内占用的内存块
property_list_t *baseProperties; ///+ 编译期间就确定的属性
...
...
}
再看一眼 class_rw_t
struct class_rw_t {
uint32_t flags;
uint16_t witness;
uint16_t index; ///+ realizeClassWithoutSwift 中通过 chooseClassArrayIndex 指定
explicit_atomic<uintptr_t> ro_or_rw_ext; ///+ 指针联合体:class_ro_t 或者 class_rw_ext_t
Class firstSubclass; //+ 子类
Class nextSiblingClass; //+ 兄弟类
}
很明显,没有太多重点,那看一下 class_rw_rext_t
:
struct class_rw_ext_t {
DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
class_ro_t_authed_ptr<const class_ro_t> ro; ///+ class_ro_t 指针。realizeClassWithoutSwift 中开辟 rw 时设置
method_array_t methods; ///+ 所有方法列表
property_array_t properties; ///+ 所有属性
protocol_array_t protocols; ///+ 所有属性
const char *demangledName; ///+ 名字
uint32_t version;
};
这里只是看一下这些结构体而已,真正初始化是在 2 类是如何被加载的 中的。
4.2 对象
id
是一个通用 OC 对象。那么 id
又是什么呢?
typedef struct objc_object *id;
struct objc_object {
private:
char isa_storage[sizeof(isa_t)];
...
...
}
也就是说,id
就是一个 objc_object
结构体指针,其内部仅存储一个 isa_t
结构体(可查看 **1.2 isa)。
再看一下类:
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA;
Class superclass; /// 父类指针
cache_t cache; /// 缓存该类已经用过的函数指针
class_data_bits_t bits; /// 存储该类的方法、属性、遵循的协议列表等
}
也就是说,类本身也是一个对象。
其实,OC 中万物是对象,block、protocol 均为对象。
看看搬运来的 描述上述结构体之间关系的类图 :
这就是 OC 中各类结构体之间的关系。
4.3 metaclass
什么是 metaclass
? 2 类是如何被加载的 有这样一些代码:
///+ 在 realizeClassWithSwift 方法中
///+ 实现其元类
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
cls->initClassIsa(metacls);
///+ 在 load_categories_nolock 方法中
///+ 将协议中的类方法,类属性附加到元类中
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties)) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
}
可以看到,class 的 isa 指向其元类,而协议中的类方法与类属性是添加到元类中的。再结合 3 方法调用,在进行方法查找时,实例方法在类的方法列表中查找,类方法在元类的方法列表中查找。这两者查找方法是不是完全相同,都是在 instance.isa.data.methods
中进行查找,两者一致了。
所以,元类的存在就是为了消息转发机制的统一性而设计的。
4.4 load
与 initialize
+load
在 2 类是如何加载的 中有一个流程 load_image,其主要作用就是执行 +load
方法。
该流程是在 main 函数之前执行的,它会查找所有类与分类的 +load
方法并执行,先类再分类。
而且是按照 父类 > 子类 的顺序递归执行的,也就是说,+load
方法最好不要调用 super,如果调用了那么父类中的流程将会执行很多次。
对于没有继承关系的类来说,其 +load
的调用顺序取决于 Compile Sources 的排列顺序。category 的 +load
方法的调用顺序也是如此。
+initialize
而在 3 方法 中有一个流程 lookUpImpOrForward,其主要作用是查找方法的 imp。
其中有这样一句:cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
,这一个句的主要作用就是调用 +initialize
方法。
来看一下 realizeAndInitializeIfNeeded_locked
:
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
lockdebug::assert_locked(&runtimeLock);
///+ 如果类还未实现,则实现它。内部调用 realizeClassWithoutSwift
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
if (slowpath(initialize && !cls->isInitialized())) {
///+ 初始化该类
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen.
///+ 如果当前接收到的消息就是 +initialize,再完成该流程之后会再次发送 +initialize 消息。
}
return cls;
}
嗯,继续往里边走
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
再往里边走:
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lockdebug::assert_locked(&lock);
///+ 如果已经初始化过了,则忽略
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
// Find the non-meta class for cls, if it is not already one.
// The +initialize message is sent to the non-meta class object.
///+ 获取原始的类
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// Realize the non-meta class if necessary.
if (nonmeta->isRealized()) {
///+ nometa 是一个类,已经被实现了,所以这不需要做任何事情
lock.unlock();
} else {
///+ 如果该类还没实现,那么就实现它:内部会调用 realizeClassWithoutSwift
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
///+ 获取 nometa 的类
cls = object_getClass(nonmeta);
}
///+ 初始化这个原始的类,调用 +initialize
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
看一下简化版的 initializeNonMetaClass
:
void initializeNonMetaClass(Class cls)
{
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
///+ 来初始化该类之前,一定要确保父类已经完成初始化操作
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
///+ 递归调用,传入父类
initializeNonMetaClass(supercls);
}
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
///+ 标记该类正在初始化 RW_INITIALIZING
cls->setInitializing();
reallyInitialize = YES;
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
///+ 已经成功将该类标记为正在初始化。执行初始化操作。
// Record that we're initializing this class so we can message it.
///+ 记录当前线程正在初始化该类
_setThisThreadIsInitializingClass(cls);
///+ 在允许多进程 fork() 执行的情况下,子线程完成后可以直接标记为已初始化
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
///+ 抛出异常的 +initialize 调用才被认为是一次完整且成功的的
@try
{
///+ 调用 +initialize。
///+ 这里发送 @selector(initialize) 消息,会再走一次这个流程
callInitialize(cls);
}
@catch (...) {
@throw;
}
@finally
{
// Done initializing.
///+ 完成初始化操作
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
...
}
else if (cls->isInitialized()) {
...
}
else {
...
}
}
从上边源码可以看到,在调用某个类的 +initialize
之前,会先调用其父类的 +initialize
方法。
好,回想一下: lookUpImpOrForward
是在接收消息时才会走到的流程,这就意味着 +initialize
是在类接收消息才会调用的。
另外,在将分类附加到的过程中,会将分类中的所有方法插入到类方法列表的最前边。这就意味着分类中的 +initialize
方法会覆盖类本身的 +initialize
方法,其覆盖顺序也取决的他们在 Compile Sources 中的顺序,后者覆盖前者。
4.5 一些可能会用的 runtime 方法
按照 runtime.h 排序:
function | note |
---|---|
Class object_getClass(id obj) |
传入对象获取其类,传入类对象则获取元类 |
Class object_setClass(id obj, Class cls) |
设置对象的类型,返回原本的类型,KVO 就是这样通过替换 isa 实现的 |
BOOL object_isClass(id obj) |
判断传入对象是否是类 |
Class objc_getClass(const char *name) |
根据字符串获取类 |
Class objc_getMetaClass(char *name) |
根据字符串获取元类 |
char *class_getName(Class cls) |
获取类名 |
BOOL class_isMetaClass(Class cls) |
判断传入对象是否是元类 |
Class class_getSuperclass(Class cls) |
获取类的父类 |
size_t class_getInstanceSize(Class cls) |
获取该类实例占用的内存大小 |
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) |
获取该类的成员变量列表 |
Method class_getInstanceMethod(Class cls, SEL name) |
根据 SEL 获取实例方法的 method 结构体 |
Method class_getClassMethod(Class cls, SEL name) |
根据 SEL 获取类方法的 method 结构体 |
IMP class_getMethodImplementation(Class cls, SEL name) |
根据 SEL 获取实力方法的具体实现 IMP |
BOOL class_respondsToSelector(Class name, SEL name) |
判断该类实例对象是否能响应指定方法 |
Method *class_copyMethodList(Class cls, unsigned int *outCount) |
获取类的实例方法列表 |
BOOL class_conformsToProtocol(Class cls, Protocol *protocol) |
判断类是否符合指定协议 |
Protocol **class_copyProtocolList(Class cls, unsigned int *outCount) |
获取类符合的协议列表 |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) |
获取类的属性列表 |
BOOL class_addMethod(Class cls, SEL name, IMP imp, char *types) |
向类中添加一个方法 |
IMP class_replaceMethod(Class cls, SEL name, IMP imp, char *types) |
替换方法的实现 |
Class objc_allocateClassPair(Class superclass, char *name, size_t extraBytes) |
动态创建一个新的类及其元类,KVO 用到 |
void objc_registerClassPair(Class cls) |
向 runtime 动态注册由 objc_allocateClassPair 创建的类,KVO 用到 |
Class objc_duplicateClass(Class original, char *name, size_t extraBytes) |
根据原始类,新类名以及额外空间动态创建一个新类,KVO 用到,不要直接调用 |
void objc_disposeClassPair(Class cls) |
销毁一个类及其元类,必须是 objc_allocateClassPair 创建的类,KVO 用到 |
SEL method_getName(Method m) |
获取指定 method 的名字 |
IMP method_getImplementation(Method m) |
获取 method 的具体实现 |
char *method_getTypeEncoding(Method m) |
获取 method 的类型 |
unsigned int method_getNumberOfArguments(Method m) |
获取 method 的参数数量 |
char *method_copyReturnType(Method m) |
获取 method 的返回类型 |
char *method_copyArgumentType(Method m, unsigned int index) |
获取 method 指定下标参数的类型 |
void method_getReturnType(Methhod m, char *dst, size_t dst_len) |
获取 method 的返回类型,同 method_copyReturnType |
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) |
获取 method 指定下边参数的类型,同 method_copyArgumentType |
IMP method_setImplementation(Method m, IMP imp) |
设置 method 的具体实现,返回原本的实现 |
void method_exchangeImplementations(Method m1, Method m2) |
交换两个 method 的具体实现 |
BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) |
判断 proto 是否符合 other |
IMP imp_implementationWithBlock(id block) |
通过 block 创建一个 IMP |
id imp_getBlock(IMP anImp) |
获取由 imp_implementationWithBlock 创建时的 block |
BOOL imp_removeBlock(IMP anImp) |
释放由 imp_implementationWithBlock 创建时的 block |
id objc_loadWeak(id location) |
获取弱引用指向的原始对象 |
id objc_storeWeak(id *localtion, id obj) |
将一个新的弱引用指针存储到列表中 |
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) |
添加关联对象 |
id objc_getAssociatedObject(id object, void * key) |
获取对应 key 的关联对象 |
void objc_removeAssociatedObjects(id object) |
移除所有关联对象 |