Ali, Byte: A set of efficient iOS interview questions (1- runtime structure model-2)

There are three full versions of this article:

3 methods

First look at the structure of the method:

struct method_t {
    SEL name;
    const char *types;
    MethodListIMP imp;
};
复制代码

To put it simply, to shed the gorgeous coat, the method is realized by the name nameand type .typesimp

All method calls of OC are actually sending messages, which can be objc_msgSendproved by clang -rewrite-objc main.m -o main.cppconverting OC files to C++ files through , so the focus is objc_msgSendon .

3.1 Method callobjc_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
复制代码

This is the source code objc_msgSendin . For specific analysis, please refer to # objc-msg-arm64 source code in-depth analysis .

Why is it written in assembly?

  1. high speed
  2. Applicable to any number of parameters, any parameter type, and any return value type (C/C++, OC can't do it)

Going back objc_msgSend, take a look at LGetIsaDonethe implementation when the method imp is not found in the cache __objc_msgSend_uncached, and what does it look like?

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

MethodTableLookup ///+ 在方法列表中查找
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached
复制代码

MethodTableLookupIn the search , look at the source code again:

.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 Method query

Well, you can lookUpImpOrForwardsee , the incoming behavior is 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;
}
复制代码

The summary process is: 0. Preparatory operation: implement the class and execute the +initilize method

  1. Find methods along the inheritance chain (up to NSObject) with the priority of cache > method list
  2. If not found, press LOOKUP_RESOLVERthe mark to try to call +resolveMethod to resolve and return directly
  3. After finding the method, add the method to the cache and return
  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) Pass in an object to get its class, pass in a class object to get the metaclass
Class object_setClass(id obj, Class cls) Set the type of the object and return the original type. This is how KVO is implemented by replacing isa
BOOL object_isClass(id obj) Determine whether the incoming object is a class
Class objc_getClass(const char *name) get class from string
Class objc_getMetaClass(char *name) get metaclass from string
char *class_getName(Class cls) get class name
BOOL class_isMetaClass(Class cls) Determine whether the incoming object is a metaclass
Class class_getSuperclass(Class cls) Get the parent class of the class
size_t class_getInstanceSize(Class cls) Get the memory size occupied by this class instance
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) Get the list of member variables of this class
Method class_getInstanceMethod(Class cls, SEL name) Obtain the method structure of the instance method according to SEL
Method class_getClassMethod(Class cls, SEL name) Obtain the method structure of the class method according to SEL
IMP class_getMethodImplementation(Class cls, SEL name) According to the specific implementation of the strength method obtained by SEL IMP
BOOL class_respondsToSelector(Class name, SEL name) Determine whether the instance object of this class can respond to the specified method
Method *class_copyMethodList(Class cls, unsigned int *outCount) Get a list of instance methods of a class
BOOL class_conformsToProtocol(Class cls, Protocol *protocol) Determine whether a class conforms to a specified protocol
Protocol **class_copyProtocolList(Class cls, unsigned int *outCount) Get a list of protocols that a class conforms to
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) Get a list of properties of a class
BOOL class_addMethod(Class cls, SEL name, IMP imp, char *types) Add a method to the class
IMP class_replaceMethod(Class cls, SEL name, IMP imp, char *types) Implementation of the replacement method
Class objc_allocateClassPair(Class superclass, char *name, size_t extraBytes) Dynamically create a new class and its metaclass, KVO uses
void objc_registerClassPair(Class cls) Dynamically register classes objc_allocateClassPaircreated , used by KVO
Class objc_duplicateClass(Class original, char *name, size_t extraBytes) Create a new class dynamically according to the original class, new class name and extra space, KVO is used, do not call directly
void objc_disposeClassPair(Class cls) To destroy a class and its metaclass, it must be a objc_allocateClassPaircreated class, KVO is used
SEL method_getName(Method m) Get the name of the specified method
IMP method_getImplementation(Method m) Get the concrete implementation of the method
char *method_getTypeEncoding(Method m) Get the type of method
unsigned int method_getNumberOfArguments(Method m) Get the number of parameters of the method
char *method_copyReturnType(Method m) Get the return type of the method
char *method_copyArgumentType(Method m, unsigned int index) Get the type of the specified subscript parameter of the method
void method_getReturnType(Methhod m, char *dst, size_t dst_len) Get the return type of the method, same asmethod_copyReturnType
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len) Get the method to specify the type of the parameters below, same asmethod_copyArgumentType
IMP method_setImplementation(Method m, IMP imp) Set the specific implementation of the method and return to the original implementation
void method_exchangeImplementations(Method m1, Method m2) Exchange the concrete implementation of two methods
BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) Determine whether proto conforms to other
IMP imp_implementationWithBlock(id block) Create an IMP by block
id imp_getBlock(IMP anImp) Get the block imp_implementationWithBlockcreated
BOOL imp_removeBlock(IMP anImp) Release the block imp_implementationWithBlockcreated
id objc_loadWeak(id location) Get the original object pointed to by the weak reference
id objc_storeWeak(id *localtion, id obj) store a new weak reference pointer into the list
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) Add associated object
id objc_getAssociatedObject(id object, void * key) Get the associated object corresponding to the key
void objc_removeAssociatedObjects(id object) Remove all associated objects

Guess you like

Origin juejin.im/post/7218916720324788279