阿里、字节:一套高效的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源码深入分析

为什么是汇编编写呢?

  1. 速度快
  2. 适用于任何参数个数,任何参数类型,任何返回值类型(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 方法

  1. 沿着继承链(一直到 NSObject)分别以 cache > 方法列表的优先级查找方法
  2. 若未找到,则按 LOOKUP_RESOLVER 标记尝试调用 +resolveMethod 来解决且直接返回
  3. 找到方法后,将该方法添加至缓存并返回
  4. 若未找到方法,按 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);
}
复制代码
  1. 如果是调用实例方法,则调用 +resolveInstanceMehtod: 尝试解决
  2. 如果是调用类方法,先调用 +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);
}
复制代码
  1. 首先确保类是否实现在 +resolveInstanceMethod: 方法,如果未实现则直接 return(但 NSObject 中会有默认实现)
  2. 调用 +resolveInstanceMethod: (可以再该方法内动态添加一个 IMP)
  3. 再次通过原本的 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];
}
复制代码

好了总结一下:

  1. objc_msgSend
  2. NilOrTagged
  3. CacheLookup
  4. MethodTableLookup
  5. lookUpImpOrForward /// 找到 IMP 则调用
  6. resolveMethod,回调到 5
  7. forwardingTargetForSelector /// 找到“替死鬼”就让它处理
  8. methodSignatureForSelector
  9. forwardInvocation /// 让下一个“替死鬼”处理,没找到就崩溃

或者看一下大佬的图

4 其他

4.1 class_rw_tclass_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 loadinitialize

  • +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) 移除所有关联对象

猜你喜欢

转载自juejin.im/post/7218916720324788279