iOS 源码中十大有趣方法

罗列了 iOS 底层源码基础的比较有意思的十个函数/方法/结构。

No.1: search_method_list_inline

    耳朵过来,我给你说个方法
    `辣锅方法?`
    你怎么知道?就是这个方法!

第一个查找方法的方法,名字就很魔幻,这个方法在各大流程里面几乎都有出现,在一个 entsize() 类型中寻找 sel 的方法,这个也是底层上关于二分法的一个应用

/***********************************************************************
 * search_method_list_inline
 **********************************************************************/
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

No.2: .macro CacheLookup

别啥都问我、先寄几个儿看看行吗!?脑子 wa ta la?!

第二个是一个汇编宏,msgSend() 进入之后也就做了三件事情,第一判空,第二拿isa,第三就是下面这个方法,汇编实现的 macro CacheLookup

.macro CacheLookup

LLookupStart$1:

    // p1 = SEL, p16 = isa  --- #define CACHE (2 * __SIZEOF_POINTER__),其中 __SIZEOF_POINTER__表示pointer的大小 ,即 2*8 = 16
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets 从x16(即isa)中平移16字节,取出cache 存入p11寄存器 -- 
//--- isa距离cache 正好16字节:isa(8字节)-superClass(8字节)-cache(mask高16位 + buckets低48位)
    ldr p11, [x16, #CACHE]

//---- 64位真机
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//--- p11(cache) & 0x0000ffffffffffff ,mask高16位抹零,得到buckets 存入p10寄存器-- 即去掉mask,留下buckets

    and p10, p11, #0x0000ffffffffffff   // p10 = buckets

//--- p11(cache)右移48位,得到mask(即p11 存储mask),mask & p1(msgSend的第二个参数 cmd-sel) ,
//--- 得到sel-imp的下标index(即搜索下标) 存入p12(cache insert写入时的哈希下标计算是 通过 sel & mask,读取时也需要通过这种方式)
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask

//--- 非64位真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
    and p12, p1, p11                // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif


    //--- p12是下标 p10是buckets数组首地址,下标 * 1<<4(即16) 得到实际内存的偏移量,通过buckets的首地址偏移,获取bucket存入p12寄存器
    //--- LSL #(1+PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小 -- 相当于mask = occupied -1-- _cmd & mask -- 取余数
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

    //--- 从x12(即p12)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel)
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
//--- 比较 sel 与 p1(传入的参数cmd)
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    //--- 如果不相等,即没有找到,请跳转至 2f
    b.ne    2f          //     scan more
    //--- 如果相等 即cacheHit 缓存命中,直接返回imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    //--- 如果一直都找不到, 因为是normal ,跳转至__objc_msgSend_uncached
    CheckMiss $0            // miss if bucket->sel == 0
    //--- 判断p12(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素,),如果等于,则跳转至第3步
    cmp p12, p10        // wrap if bucket == buckets
    //--- 定位到最后一个元素(即第一个bucket)
    b.eq    3f
    //--- 从x12(即p12 buckets首地址)- 实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    //--- 跳转至第1步,继续对比 sel 与 cmd
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.
//--- 再查找一遍缓存()
//--- 拿到x12(即p12)bucket中的 imp-sel 分别存入 p17-p9
    ldp p17, p9, [x12]      // {imp, sel} = *bucket
//--- 比较 sel 与 p1(传入的参数cmd)
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    //--- 如果不相等,即走到第二步
    b.ne    2f          //     scan more
    //--- 如果相等 即命中,直接返回imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    //--- 如果一直找不到,则CheckMiss
    CheckMiss $0            // miss if bucket->sel == 0
    //--- 判断p12(下标对应的bucket) 是否 等于 p10(buckets数组第一个元素)-- 表示前面已经没有了,但是还是没有找到
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    //--- 从x12(即p12 buckets首地址)- 实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    //--- 跳转至第1步,继续对比 sel 与 cmd
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    //--- 跳转至JumpMiss 因为是normal ,跳转至__objc_msgSend_uncached
    JumpMiss $0

.endmacro

No.3: lookUpImpOrForward

    找到了找到了,马上马上,我马上就到了。(赶紧下个单)

方法的慢速查找,贯穿了整个 runtime 的核心方法,它就是位于objc-runtime-new.mm文件中的 loopUpImpOrForward 方法:

/***********************************************************************
* lookUpImpOrForward.
* 标准的 IMP 查找流程的.
* 不包括 LOOKUP_INITIALIZE: 尝试去避免 +initialize (但是有时候会失败)
* 不包括 LOOKUP_CACHE: skips optimistic unlocked lookup (but uses cache elsewhere)
* 大多数调用者应该使用 LOOKUP_INITIALIZE 和 LOOKUP_CACHE
* inst 是 cls 或其子类的实例, 如果未知,则为nil。.
*   如果 cls 是未初始化的元类,则非 null 实例会更快。
* 可能会返回_objc_msgForward_impcache。
* 供外部使用的IMP必须转换为_objc_msgForward或_objc_msgForward_stret。
* 如果根本不想转发,请使用LOOKUP_NIL。
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 首先上来就先给 forward_imp 做了一个赋值,这个赋值跟下去,是在汇编里面
    // 实现体就是一个 b 指令调整到 objc_msgforward
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    // runtimeLock在isRealized和isInitialized检查期间保持,
    // 以防止与并发实现竞争。

    // 因为考虑到有方法的添加,所以在方法的搜索过程中,
    // 要添加 runtimeLock:用来保证 methodLookup 和 cacheFill 的原子性
    
    // 否则,category 的方法可能在添加后永久地被忽略
    // 因为有可能在category添加方法之后,旧方法马上进行了方法缓存
//    (相当于以后每次都会去执行缓存的旧方法,不会找到category添加的新方法)

    runtimeLock.lock();

    //  我们不希望人们能够制作看起来像类但实际上不是一个二进制二进制Blob并进行CFI攻击。
    //
    //  为了使这些操作更困难,我们希望确保这是一个内置于二进制文件中或通过 objc_duplicateClass , objc_initializeClassPair 或 objc_allocateClassPair 合法注册的类。
    //
    // TODO: 在流程启动期间,此检查的成本很高。
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock 可能已被删除,但现在再次被锁定
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock 可能已被删除,但现在再次被锁定

        // 如果sel == initialize,则class_initialize将发送+ initialize,然后在此过程完成后,Messenger将再次发送+ initialize。 当然,如果未从Messenger调用此命令,则不会发生。. 2778172
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // 在我们获取锁之后,用于重新查找类的缓存的代码很快就出现了,
    // 但是在绝大多数情况下,证据表明这在大多数情况下都是未命中的,因此会浪费时间。
    //
    // 没有执行某种缓存查找的唯一调用此方法的代码路径是 class_getInstanceMethod()。

    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        if (slowpath((curClass = curClass->superclass) == nil)) {
            // 找不到实现,方法解析器也无济于事
            // 使用 forwarding.
            imp = forward_imp;
            break;
        }

        // 如果 Superclass 链中存在循环,则停止。
        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.
            // 停止搜索,但不要缓存;请先调用此类的方法解析器。
            break;
        }
        if (fastpath(imp)) {
            // 在超类中找到了方法。在这个类中缓存它。
            goto done;
        }
    }

    // 找不到实现。尝试一次方法解析器。
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

No.4: realizeClassWithoutSwift

    送你一个类!啥?你还要 swift 的?算了,给你 void 星吧

著名的创建 Class 的方法,在 loadimages 的时候被 dyld 调用。

另外,其实在上面也调用过它,就是第一次给懒加载的类发送消息的时候也调用过,大概就是这样的:

`lookUpImpOrForward` 
    --> `if(!cls->isRealized())` 
        --> `realizeClassMaybeSwiftAndLeaveLocked`
            --> `realizeClassMaybeSwiftMaybeRelock` 
                --> `realizeClassWithoutSwift(cls, nil)`
/***********************************************************************
* realizeClassWithoutSwift
* 对类 cls 进行首次初始化,包括分配其 rw 的数据。
* 不执行任何Swift端初始化。
* 返回该类的真实类结构。
* 锁定:runtimeLock必须由调用方写锁定
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    // fixme 验证类不是在共享高速缓存的未dlopened部分?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // 这是将来的类结构. rw 以已经分配好了
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. 分配可写的类数据。
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif

    // 选择此类的索引。
    // 如果没有更多索引可用,则设置cls-> instancesRequireRawIsa
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // 接下来是实现超类和元类(如果尚未实现的话)。
    // 对于根类,需要在上面设置了RW_REALIZED之后执行此操作。
    // 对于根元类,需要在选择类索引之后执行此操作。
    // 这假定这些类都不包含Swift内容,或者已经调用了Swift的初始化程序。
    //  fixme如果我们添加对Swift类的ObjC子类的支持,则假设将是错误的。
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // 元类不需要非指针 ISA 的任何功能
        // 这样为类在 objc_retain/objc_release 提供便捷。
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa();
    } else {
        // 在某些类和/或平台上禁用 nonPointer 的 isa。
        // 设置instanceRequireRawIsa。
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            // Non-pointer isa 被环境或应用程序SDK版本禁用
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            // 对于LIbDebug等的黑客攻击——ISA也充当VTABLE指针
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {

            //这也由addSubclass()传播
            //但非指针式isa安装程序需要更早。
            //特殊情况:instancesRequireRawIsa不从根类传播到根元类
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // 在重新映射时更新超类和元类
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // 协调实例变量偏移/布局。
    // 这可能会重新分配 class_ro_t, 更新 ro 变量
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 设置fastInstanceSize(如果尚未设置)。
    cls->setInstanceSize(ro->instanceSize);

    // 复制一些标志 from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // 从ro或超类传播相关的objects forbidden标志。
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // 将这个类连接到它的超类的子类列表
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 把 categorie 们加过来
    methodizeClass(cls, previously);

    return cls;
}

No.5: initializeNonMetaClass --> callInitialize(cls)

    你加班能不能不唱歌?再唱我把你!再,我就,把你!婴内特!

是不是见过有人分析,什么时候调用 load 什么时候调用 initialize 方法,下面这个 initializeNonMetaClass 就是唯一一次调用 callInitialize(cls) 方法的方法,下面发出来两个方法:

initializeNonMetaClass:

/***********************************************************************
* class_initialize.  按需向任何未初始化的类发送“+initialize”消息。
* 首先强制初始化超类。
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // 在开始初始化cls之前,确保super已完成初始化。
    // 参见上面关于死锁的注释。
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    // 尝试原子操作设置 CLS_INITIALIZING.
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;

            // 获取一个将在保持锁的情况下初始化函数的副本。


            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    
    if (reallyInitialize) {
        // 我们成功地设置了CLS_初始化位。初始化类。
        
        // 记录我们正在初始化这个类,这样我们就可以给它发消息了。
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        for (auto callback : localWillInitializeFuncs)
            callback.f(callback.context, cls);

        // Send the +initialize message.
        // 注意,这个 +initialize 是发送给superclass的,如果这个class没有实现 +initialize.2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                         objc_thread_self(), cls->nameForLogging());
        }

        // 异常:抛出异常的 +initialize 调用被视为完整且成功的 +initialize。
        //
        // 只有 __OBJC2__ 添加这些处理程序。 !__OBJC2__ 有一个引导问题与CF调用objc_exception_set_functions()相比。
#if __OBJC2__
        @try
#endif
        {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        //无法设置初始化,因为已设置初始化。
        //如果此线程设置较早,则正常继续。
        //如果其他线程设置了它,请在初始化完成之前阻塞它。
        //如果在这里初始化更改为INITIALIZED是可以的,
        //因为在阻塞之前,我们安全地检查锁内部是否已初始化。
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            // 我们在fork()的子级,面对一个类,该类在fork()被调用时正被其他线程初始化。
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
    
    else if (cls->isInitialized()) {
        //Set CLS_INITIALIZING 失败,因为其他人已经初始化了该类。正常继续。
        //注意:此检查必须在 IsInitialization 案例之后。
        //否则:另一个线程正在初始化这个类, ISINITIALIZED 返回 false,跳过这个逻辑,然后另一个线程完成初始化并设置initialization=no和INITIALIZED=yes。
        //跳过ISinitialization子句。可怕地死去。
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

callInitialize(cls):

// 经典的 callInitialize 方法,就是往类里调用 initialize 方法
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

No.6: _objc_Init

    OC,我们重新开始吧?
    嗯?你说的是从哪个新?DyldStart?ObjcInit?Main?
    当然是你的 _objc_Init!

有没有经常看人家说main函数是程序的入口,那么main之前做了什么?

dyldStart
    调用了dydldMian
        initializeMainExecutable()
        //上一行之后执行
        objeInit

没错,这个 objcinit是真正进入了我们的objc项目,也就是我们常说的runtime 的层面才开始主责做的事情,虽然之前也调用了很多objc 的东西,但是大都是由dyld主要负责的。

这个方法很短:

/***********************************************************************
* _objc_init
* Bootstrap初始化。 用dyld注册我们的图像通知程序。
* 在库初始化之前由libSystem调用
**********************************************************************/

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

这里有一个车 _dyld_objc_notify_register 这里简单来说,dyld放了一个回调的方法,里面有我们dyly唤醒你之前发现的所有可行性文件里面的需要调用load 的地方,你醒来之后记得都去调用一下这些非懒加载的类。属于dyld和objcinit两个打个交叉配合,把初始化的流程全部搞起来。

No.7: dispatch_object_t

    喂?组长你说GCD这源码咋写的这 sa* 内?

我们 GCD 的对象就不是对象吗?

第七点看这个 title : dispatch_object_t,这个东西四舍五入就是一个 object_t 啊对不对?

其实可以说就是的,这个 dispatch_object_tlibspatch 里面的一个很基础的抽象基类,注意抽象两个字,他不可以实例化,为什么呢?C一个基础语言,一个结构体为啥不能实例化?

用我们最熟悉的 ISA 来解释这个 dispatch_object_t,说ISA是指针的其实说法是错的,ISA是一个联合体union,他要么是bits位域类型,要么是isa指针类型。你不能说一个实例化出来的ISA是一个不确定类型的联合体,哪怕人家是个空呢。

所以说我们看一下这个结构的定义:
他的定义分两部分,一部分是公开的,一部分是internal内部使用的。就简单看外部使用的,有兴趣的可以研究内部的所有对象。

/*!
 * @typedef dispatch_object_t
 *
 * @abstract
 * 所有分派对象的抽象基类型。
 * 类型定义的详细信息是特定于语言的。specific.
 *
 * @discussion
 * 通过调用 dispatch_retain() 和 dispatch_release() 来计算分派对象的引用数。
 */
typedef union {
    struct _os_object_s *_os_obj;
    struct dispatch_object_s *_do;
    struct dispatch_queue_s *_dq;
    struct dispatch_queue_attr_s *_dqa;
    struct dispatch_group_s *_dg;
    struct dispatch_source_s *_ds;
    struct dispatch_channel_s *_dch;
    struct dispatch_mach_s *_dm;
    struct dispatch_mach_msg_s *_dmsg;
    struct dispatch_semaphore_s *_dsema;
    struct dispatch_data_s *_ddata;
    struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;

这里面定义的每一行 struct 都是他的可能子类,这就是面向对象的多态的实现,不需要父子类的继承,仅通过一个结构体的声明就可以完成!

No.8: thinking...

NFY:not finished yet:⑧⑨ 待补充,想写关联对象什么的没什么意思,runloop 的那个 dowhile 大循环没什么意思,我再思考思考🤔些什么比较有意思吧。

No.9: thinking...

No.10: longest method in objc

组长,我写的方法比这个长多了,你怎么不夸我呢?
我。。。

现在面试问的问题那么多底层的问题,我就不是很理解,万一对面的人回答不上来尴尬怎么办,我想到一个问题,既然要求候选人读源码,就问问候选人,

源码里你见过最长的一个方法是什么? 候选人可能就会觉得你这个公司特别的温暖...(什么鬼...)

这个方法就是最后一条,这个方法就是 dyld 的 main 方法...:

篇幅实在太长了,我就不粘贴出来了,因为一共六百多行!

dyld2.cpp,想看的话了这里看看

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{

//............

//** 检查环境变量
// 把数组参数传递进函数体,然后for循环判断环境变量
    {
//** 检查环境变量 // 把数组参数传递进函数体,然后for循环判断环境变量
        checkEnvironmentVariables(envp);
//** 默认未初始化的后备路径
        defaultUninitializedFallbackPaths(envp);
    }


//............

    // load shared cache  加载 shared cache 检查共享缓存区域是否开启可用
    checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
    if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
        if ( sSharedCacheOverrideDir)
            mapSharedCache();
#else
        mapSharedCache();
#endif
    }


//............

// 从 ImageLoader 变成一个 「主要可执行文件」,
// 使用 instantiateFromLoadedImage 这个方法来生成
        CRSetCrashLogMessage(sLoadingCrashMessage);
        // instantiate ImageLoader for main executable
//从 ImageLoader 变成一个 「主要可执行文件」,使用 instantiateFromLoadedImage 这个方法来生成的
        sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
        gLinkContext.mainExecutable = sMainExecutable;
        gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);

//............

        // 加载任何插入的库
        if  ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
            for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
                loadInsertedDylib(*lib);
        }
        // 记录插入的库的数量,以便统一搜索将先查看插入的库,然后是main,然后是其他。
        sInsertedDylibCount = sAllImages.size()-1;


//............

        // 链接 上面的那个 「主可执行文件」
        gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
        if ( mainExcutableAlreadyRebased ) {
            // 「主可执行文件」上的先前link()已为ASLR调整了其内部指针
            //通过反基础变基来解决此问题
            sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
        }
#endif
//**!link sMainExecutable「就是主可执行文件」
        link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        sMainExecutable->setNeverUnloadRecursive();


//............

        // 链接任何插入的库
        // 在链接主可执行文件之后执行此操作,以便通过插入插入的任何dylib
        // dylib(例如libSystem)不在程序使用的dylib之前
        if ( sInsertedDylibCount > 0 ) {
            for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                ImageLoader* image = sAllImages[i+1];
//**!link 动态库
                link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
                image->setNeverUnloadRecursive();
            }
            if ( gLinkContext.allowInterposing ) {
                // 只允许动态库插入
                // 绑定所有插入的库后,注册插入信息,以便链接工作
                for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
                    ImageLoader* image = sAllImages[i+1];
                    image->registerInterposing(gLinkContext);
                }
            }
        }


//............

//仅在所有插入的 image link 完成后   执行   「弱符号绑定」
        // <rdar://problem/12186933> do weak binding only after all inserted images linked
        // 仅在所有插入的 image link 完成后   执行   「弱符号绑定」
        sMainExecutable->weakBind(gLinkContext);
        gLinkContext.linkingMainExecutable = false;


//............

        // 执行   所有的   initializers
        initializeMainExecutable(); 
// initializeMainExecutable 的 for 循环的里面,都挨个执行 runInitializers()


//............

// 从Load Command读取LC_MAIN入口,如果没有,就读取LC_UNIXTHREAD
// 这里就是 Main    所谓的「程序的入口」
        {
            // 查找「主要可执行文件」的入口点
            result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
            if ( result != 0 ) {
                // 主要可执行文件使用LC_MAIN,我们需要在libdyld中使用helper来调用main()
                if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
                    *startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
                else
                    halt("libdyld.dylib support not present for LC_MAIN");
            }
            else {
                // 主可执行文件使用LC_UNIXTHREAD,dyld需要在为main()设置的程序中“启动”
                result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
                *startGlue = 0;
            }
        }
//............

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容