阅读视图

发现新文章,点击刷新页面。

iOS底层之类的加载

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

序言

前面的文章中探究了类的结构,知道了类中都有哪些内容,那么今天就来探究一下,类到底是怎么加载进内存的呢?在什么时候加载到内存的呢?

我们定义的类.h.m文件,首先需要通过编译器生产可执行文件,这个过程称为编译阶段,然后安装在设备上加载运行。

编译

  • 预编译:编译之前的一些先前的处理工作,处理一些#开头的文件,#include#define以及条件编译等;
  • 编译:对预编译后的文件进行词法分析语法分析语义分析,并进行代码优化,生成汇编代码;
  • 汇编:将汇编文件代码转换为机器可以执行的指令,并生成目标文件.o
  • 链接:将所有目标文件以及链接的第三方库,链接成可执行文件macho;这一过程中,链接器将不同的目标文件链接起来,因为不同的目标文件之间可能有相互引用的变量或调用的函数,比如我们常用的系统库。

动态方法决议-导出.png

动态库与静态库

  • 静态库:链接阶段将汇编生成的目标文件和引用库一起链接打包到可执行文件中,如:.a.lib
    • 优点:编译成功后可执行文件可以独立运行,不需要依赖外部环境;
    • 缺点:编译的文件会变大,如果静态库更新必须重新编译;
  • 动态库:链接时不复制,程序运行时由系统加载到内存中,供系统调用,如:.dylib.framework
    • 优点:系统只需加载一次,多次使用,共用节省内存,通过更新动态库,达到更新程序的目的;
    • 缺点:可执行文件不可以单独运行,必须依赖外部环境;

系统的framework是动态的,开发者创建的framework是静态的

3864017-0c111edb6fed43b2.webp

dyld动态链接器

  • dyld是iOS操作系统的一个重要组成部分,在系统内核做好程序准备工作之后,会交由dyld负责余下的工作。
  • dyld的作用:加载各个库,也就是image镜像文件,由dyld从内存中读到表中,加载主程序,link链接各个动静态库,进行主程序的初始化工作。

3864017-0c1deb5c8c79c239.png

dyld负责链接、加载程序,但是dyld的探索过程比较繁琐就不详细展开了,直接进入类的加载核心_objc_init方法探索。

_objc_init探索

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();      // 关于线程key的绑定
    static_init();   // 运行C++静态构造函数
    runtime_init();  // runtime运行时环境初始化
    exception_init();// 初始化libobjc库的异常处理
#if __OBJC2__
    cache_t::init(); // 缓存条件初始化
#endif
    _imp_implementationWithBlock_init(); // 启动回调机制
    
    // 注册处理程序,以便在映射、取消映射和初始化objc图像时调用,仅供运行时Runtime使用
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = **true**;
#endif
}

environ_init环境变量

/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
************************************************************************ ** **/
void environ_init(void) 
{
// 部分核心代码
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }

        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
//            if (opt->internal
//                && !os_variant_allows_internal_security_policies("com.apple.obj-c"))
//                continue;
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}

通过控制PrintHelpPrintOptions可以打印当前环境变量的配置信息,我们在源码环境中把for循环代码复制出来改一下,运行

image.png

image.png

也可以通过终端命令export OBJC_HELP = 1,在终端上显示

image.png

可以通过Edit shceme,在Environment Variables配置相关变量

  • OBJC_DISABLE_NONPOINTER_ISA: isa的优化开关,如果YES表示不使用,就是存指针;如果NO开启指针优化,为nonpointer isa;
  • OBJC_PRINT_LOAD_METHODS:是否开启打印所有load方法,可以判断哪些类使用了load方法,做相应的优化处理,以优化启动速度;

image.png

tls_init线程key的绑定

tls_init关于线程key的绑定,比如每个线程数据的析构函数

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

static_init运行C++静态构造函数

运行C++静态构造函数。 libcdyld调用静态构造函数之前调用objc_init(),因此我们必须自己执行。

static void static_init()
{
    size_t count1;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count1);
    for (size_t i = 0; i < count1; i++) {
        inits[i]();
    }
    size_t count2;
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count2);
    for (size_t i = 0; i < count2; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
#if DEBUG
    if (count1 == 0 && count2 == 0)
        _objc_inform("No static initializers found in libobjc. This is unexpected for a debug build. Make sure the 'markgc' build phase ran on this dylib. This process is probably going to crash momentarily due to using uninitialized global data.");
#endif
}

runtime_init运行时环境初始化

Runtime运行时环境初始化,主要是unattachedCategoriesallocatedClasses两张表的初始化

void runtime_init(void)
{
    objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;
    objc::unattachedCategories.init(32); // 分类表
    objc::allocatedClasses.init();       // 已开辟类的表
}

exception_init 异常系统初始化

初始化libobjc的异常处理系统,由map_images()调用。注册异常处理的回调,从而监控异常的处理

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

异常处理系统初始化后,当程序运行不符合底层规则时,比如:数组越界方法未实现等,系统就会发出异常信号。

image.png 有异常发生时,uncaught_handler函数会把异常信息e抛出

image.pnguncaught_handler就是这里传进来的fn,这个fn就是我们检测异常的句柄。我们可以自定义异常处理类,通过NSSetUncaughtExceptionHandler把我们句柄函数地址传进去

image.png 有异常发生时,系统会回调给我们异常exception,然后自定义上传等处理操作。 image.png

cache_t::init缓存条件初始化

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
        count++;
    }
    kr = task_restartable_ranges_register(mach_task_self(),
                                 objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

image.png

_imp_implementationWithBlock_init

通常情况下,这没有任何作用,因为所有的初始化都是惰性的,但对于某些进程,我们急切地加载trampolines dylib。

在某些过程中急切地加载libobjc-tropolines.dylib。一些程序(最著名的是早期版本的嵌入式Chromium使用的QtWebEngineProcess)启用了一个限制性很强的沙盒配置文件,该文件阻止对该dylib的访问。如果有任何东西调用imp_implementationWithBlock(正如AppKit已经开始做的那样),那么我们将在尝试加载它时崩溃。在这里加载它会在启用沙盒配置文件并阻止它之前设置它。

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

_dyld_objc_notify_register

void 
_dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

_dyld_objc_notify_register中的三个参数含义如下

  • &map_imagesdyldimage加载到内存中会调用该函数
  • load_imagesdyld初始化所有的image文件会调用
  • unmap_image:将image移除时会调用

我们重点看的就是将image加载到内存中调用的函数map_image

image.pngmap_image中调用map_images_nolock

image.pngmap_images_nolock中的代码比较多,我们这里直接看重点_read_images

_read_images解读

_read_images方法中有360行代码,有点长,把里面的大括号折叠,苹果的代码流程和注释是很好的,可以先整体把握一下,里面的ts.log很清晰的告诉了我们整个流程。

void _read_images(header_info hList, uint32_t hCount, int 
totalClasses, int 
unoptimizedTotalClasses)
{
   ... //表示省略部分代码
#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++
    // 条件控制进行一次的加载
    if (!doneOnce) { ... }
    // 修复预编译阶段的`@selector`的混乱的问题
    // 就是不同类中有相同的方法 但是相同的方法地址是不一样的
    // Fix up @selector references
    static size_t UnfixedSelectors;
    { ... }
    ts.log("IMAGE TIMES: fix up selector references");
    
    // 错误混乱的类处理
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover classes");
    
    // 修复重映射一些没有被镜像文件加载进来的类
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    if (!noClassesRemapped()) { ... }
    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 修复一些消息
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
    // 当类中有协议时:`readProtocol`
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修复没有被加载的协议
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // 分类的处理
    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes.  
    if (didInitialAttachCategories) { ... }
    ts.log("IMAGE TIMES: discover categories");
    
    // 类的加载处理
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code befor
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 没有被处理的类,优化那些被侵犯的类
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes");
   ...
#undef EACH_HEADER

}
  • 条件控制,进行一次加载;
  • 修复预编译阶段的@selecter混乱问题;
  • 错误混乱的类处理;
  • 修复重新映射一些没有被镜像文件加载进来的类;
  • 修复一些消息
  • 当类里面有协议的时候:readProtocol
  • 修复没有被加载进来的协议;
  • 分类处理;
  • 类的加载处理;
  • 没有被处理的类

doneOnce加载一次

if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
        // Disable nonpointer isa if any image contains old Swift code
        for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&
                hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

#endif
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        // 小对象地址混淆
        initializeTaggedPointerObfuscator();
        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        // 容量:总数 * 4 / 3
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // 创建哈希表,用于存放所有的类
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
    }

通过对doneOnce的判断,只会进来一次条件语句,这里主要处理对类表的开辟创建处理gdb_objc_realized_classes

修复@selecter混乱问题

static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            // 从macho文件中获取方法名列表
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                // sel通过name从dyld中查找获取
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) { // 修复地址,以dyld为准
                    sels[i] = sel;
                }
            }
        }
    }

    ts.log("IMAGE TIMES: fix up selector references");

sel进行修复,因为从编译后的macho读取的sel地址不一定是真实的sel地址,在这里做修复。

错误混乱的类处理

// Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }
        // 从macho中读取的类列表
        classref_t const *classlist = _getObjc2ClassList(hi, &count);
        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            // 通过readClass,将cls的类名和地址做关联
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            // 如果不一致,则加入修复
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
           resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    ts.log("IMAGE TIMES: discover classes");
  • 通过_getObjc2ClassList从macho中读取的所有的类;
  • 遍历所有的类,通过readClass讲类的地址和类名关联;

image.png

  • popFutureNamedClass返回已实现的类,我们添加的类未实现,这里if语句不成立;
  • mangledName是有值的,调用addNamedClassname=>cls添加到命名的非元类映射中。
  • addClassTableEntry:将类添加到所有类的表中。如果addMeta为true,则自动添加类的元类。
  • 返回已处理的类

可以看出readClass函数是把传进来的cls重新映射并添加cls和其元类到所有的类中。

修复重新映射的类

类列表和非懒加载类列表仍然未被添加。 类引用和父类引用被重新映射以用于消息调度。

if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }
    ts.log("IMAGE TIMES: remap classes");

修复一些消息

// Fix up old objc_msgSend_fixup call sites | 修复旧的objc_msgSend_fixup调用站点
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            // 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

image.png

添加协议

当类里面有协议的时候,调用readProtocol绑定协议

// Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

//         Skip reading protocols if this is an image from the shared cache
//         and we support roots
//         Note, after launch we do need to walk the protocol as the protocol
//         in the shared cache is marked with isCanonical() and that may not
//         be true if some non-shared cache binary was chosen as the canonical
//         definition
        if (launchTime && isPreoptimized) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }
        bool isBundle = hi->isBundle();

        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

image.png

修复协议列表引用

上面做了协议和类的关联,这里是对协议进行重新映射

image.png

分类的处理

if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");

分类的流程是比较重要的,在《iOS底层之分类的加载》专讲一下。

类的加载处理

// Realize non-lazy classes (for +load methods and static instances)
// 实现非懒加载类(实现了+load或静态实例方法)
    for (EACH_HEADER) {
        // 通过_getObjc2NonlazyClassList获取所有非懒加载类
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // 再次添加到所有类表中,如果已添加就不会添加进去,确保整个结构都被添加
            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            // 对类cls执行首次初始化,包括分配其读写数据。不执行任何Swift端初始化。
            realizeClassWithoutSwift(cls, **nil**);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

本文重点

  • 调用nlclslist(里面是调用_getObjc2NonlazyClassList)获取所有非懒加载(non-lazy)的类;
  • 循环实现,再次添加到所有的类表中,如果已添加就不会添加进去,确保整个结构都被添加;
  • 调用realizeClassWithoutSwift对类cls执行首次初始化,包括分配其读写数据;

通过realizeClassWithoutSwift实现所有非懒加载类的第一次初始化,那我们就看realizeClassWithoutSwift如何实现的

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        // 验证已实现的类
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = cls->safe_ro();
    auto isMeta = ro->flags & RO_META; // 是否元类
    if (ro->flags & RO_FUTURE) { // future类,rw的data已经初始化
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

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

    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    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)" : "");
    }

//     Realize superclass and metaclass, if they aren't already.
//     This needs to be done after RW_REALIZED is set above, for root classes.
//     This needs to be done after class index is chosen, for root metaclasses.
//     This assumes that none of those classes have Swift contents,
//       or that Swift's initializers have already been called.
//       fixme that assumption will be wrong if we add support
//       for ObjC subclasses of Swift classes.
    // 实现父类和元类
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    // 纯isa还是nonpointer isa
#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // Metaclasses do not need any features from non pointer ISA
        // This allows for a faspath for classes in objc_retain/objc_release.
        cls->setInstancesRequireRawIsa(); // 纯指针isa
    } else {
        // Disable non-pointer isa for some classes and/or platforms.
        // Set instancesRequireRawIsa.
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;
        if (DisableNonpointerIsa) {
            // Non-pointer isa disabled by environment or app SDK version
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
        {
            // hack for libdispatch et al - isa also acts as vtable pointer
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->getSuperclass()  &&
                 supercls->instancesRequireRawIsa())
        {
            // This is also propagated by addSubclass()
            // but nonpointer isa setup needs it earlier.
            // Special case: instancesRequireRawIsa does not propagate
            // from root class to root metaclass
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

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

    // Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls); // 设置superclass指向父类
    cls->initClassIsa(metacls);   // 设置isa指向元类

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    // 方法、属性、协议、分类的实现
    methodizeClass(cls, previously);
    return cls;
}

initClassIsa时会根据nonpointer区别设置

  • 先类判断是否已经实现,如果已实现通过validateAlreadyRealizedClass验证;
  • 未实现,cls->setData(rw)处理类的data也就是rwro,初始化rw并拷贝ro数据到rw中;
  • 将事件往上层传递,来实现父类以及元类
  • 判断isa指针类型,是纯指针还是nonpointer指针,在设置isa时有不同;
  • cls->setSuperclass(supercls):设置superclass指向父类;
  • cls->initClassIsa(metacls):设置isa指向元类;
  • methodizeClass:在这里进行方法、属性、协议、分类的实现;

image.png

看一下methodizeClass的实现

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext();

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    // rwe:方法、属性、协议
    method_list_t *list = ro->baseMethods;
    if (list) {
    // 写入方法,并对方法进行排序
        prepareMethodLists(cls, &list, 1, **YES**, isBundleClass(cls), **nullptr**);
        if (rwe) rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    // 如果根类还没有额外的方法实现,那么它们将获得额外的方法。这些适用于类别替换之前。
    if (cls->isRootMetaclass()) {
        // root metaclass 根元类添加initialize初始化方法
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories. // 分类处理
    if (previously) {
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS);
        }
    }
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
   for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}
  • methodizeClass中主要就是对methodpropertyprotocol存放在rwe的处理,其实这里的rwe并没有值,因为还没有完成初始化,
  • 为根元类添加initialize初始化方法,
  • 对分类处理。

上面是非懒加载类的处理,那么懒加载类是在什么时候完成初始化的呢?根据懒加载原则,应该就是在用的时候再去调用吧,那就验证一下

源码环境中,我们在methodizeClass通过类名字加断点

先在LGTeacher里面实现+load方法,运行 image.png

这里是从_objc_init_read_images进入的

LGTeacher里面去掉+load方法,运行

image.png 这里可以看到,整个流程是在main函数里调用LGTeacheralloc方法来的,通过objc_msgSend消息查找流程的lookUpImpOrForward走到了这里。

总结

类的加载通过类是否实现+load静态实例方法,区分为懒加载类非懒加载类

  • 非懒加载类:是在启动时map_images时加载进内存的,通过_getObjc2NonlazyClassList得到所有非懒加载类,循环调用realizeClassWithoutSwiftmethodizeClass完成初始化。

  • 懒加载类:是在第一次消息发送的时候,检查类是否初始化,为完成初始化再去完成初始化流程。

关于消息发送文章:
iOS底层之Runtime探索(一)
iOS底层之Runtime探索(二)
iOS底层之Runtime探索(三)

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

分类加载的流程分析 《iOS底层之分类的加载》

以上是对iOS中类的加载通过源码的探索过程,如有疑问或错误之处请留言或私信我

iOS底层之Runtime探索(二)

iOS 全网最新objc4 可调式/编译源码
编译好的源码的下载地址

序言

在前一篇iOS底层之Runtime探索(一)中,已经知道了在selimp的整个缓存查找过程,这个过程是用汇编实现,是一个快速方法查找流程,今天就来探究一下缓存没有查找到后面的流程。

__objc_msgSend_uncached 方法探究

前面流程是对缓存进行查找imp,如果找不到就走方法__objc_msgSend_uncached方法 image.png__objc_msgSend_uncached中,共两条语句MethodTableLookupTailCallFunctionPointer x17

MethodTableLookup方法解析

image.pngMethodTableLookup中的代码逻辑

  • x0receiverx1sel
  • mov x2, x16: 将x16也就是clsx2
  • mov x3, #3: 将常量值3x3
  • 调用_lookUpImpOrForward: x0~x3是传的参数;
  • mov x17, x0: 将_lookUpImpOrForward的返回值x0x17

通过注释也可以大致看出,调用方法lookUpImpOrForward获取imp,并返回。

TailCallFunctionPointer定义

image.png 这里仅仅是对传入的$0直接调用。

我们可以总结,__objc_msgSend_uncached方法流程是通过MethodTableLookup获取imp,然后传值到TailCallFunctionPointer定义调用imp

这里的核心就是MethodTableLookup中的lookUpImpOrForward是怎么获取的imp

lookUpImpOrForward方法探究

lookUpImpOrForward通过方法命名,可以看出一些端倪,就是查找imp找不到就forward操作。

实例分析

  • 对上面定义的LGPerson添加分类,并在分类中同样实现sayHello方法;
  • NSObject添加分类,并自定义方法sayNB
  • LGTeacher定义方法sayByeBye,但不添加实现;

image.png 结果

  • sayHello调用的是LGPerson的分类方法,why?
  • 用类对象LGTeacher调用NSObjectsayNB方法成功,why?
  • 未实现方法sayByeBye报错unrecognized selector sent to instance 0x600000008000,why?

lookUpImpOrForward源码分析

源代码添加了中文注释

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //定义消息转发forward_imp
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // 判断当前类是否初始化,
    if (slowpath(!cls->isInitialized())) {
        /*
         enum {
             LOOKUP_INITIALIZE = 1,
             LOOKUP_RESOLVER = 2,
             LOOKUP_NIL = 4,
             LOOKUP_NOCACHE = 8,
         };
         */

        // 如果没有初始化 behavior = LOOKUP_INITIALIZE | LOOKUP_RESOLVER | LOOKUP_NOCACHE
        behavior |= LOOKUP_NOCACHE;
    }

    // 加锁防止多线程访问出现错乱
    runtimeLock.lock();

    // 检查当前类是否被dyld加载的类
    checkIsKnownClass(cls);

    // 如果尚未实现给定的类,则实现该类;如果尚未初始化,则初始化该类。
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);

    runtimeLock.assertLocked();
    curClass = cls;


     //在我们获取锁后,用于再次查找类缓存的代码,但对于大多数情况,证据表明这在大多数情况下都是失败的,因此会造成时间损失。
     //在没有执行某种缓存查找的情况下,唯一调用此函数的代码路径是class_getInstanceMethod()。
    for (unsigned attempts = unreasonableClassCount();;) {// 开启循环查找imp
        // 继续检查共享缓存是否有方法
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel); // cache_getImp流程获取imp
            if (imp) goto done_unlock; // 找到imp走”done_unlock“流程
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // 从curClass的method list中查找method.
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done; // 查找到imp,跳转done流程
            }

            // 未从curClass的method list中查找到对应的method,继续往父类查找,直到父类为nil
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp; // 查找不到跳出for循环
                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); // 这里curClass已指向父类,查找父类的缓存中的imp
        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)) { // 从父类缓存中查找到imp,跳转到done
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
    // No implementation found. Try method resolver once.
    // 为找到imp,开始resolveMethod_locked流程,动态方法决议
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { // 已完成类的初始化
#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); // 将获取的imp插入缓存
    }

 done_unlock:
    runtimeLock.unlock(); //runtimeLock 解锁
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

逻辑流程分析

  1. 判断当前类是否初始化,若未初始化执行behavior |= LOOKUP_NOCACHE
  2. checkIsKnownClass(cls)检查当前类是否被加载到dyld,类的加载属于懒加载,未加载这里会做加载处理;
  3. realizeAndInitializeIfNeeded_locked如果尚未实现给定的类,则实现该类;如果尚未初始化,则初始化该类;
  4. 进入for循环开始一层层遍历查找imp;
  5. curClass->cache.isConstantOptimizedCache检查共享缓存,这个时候,可能别的地方进行缓存了,如果有则直接跳转done_unlock返回imp
  6. 上面没有缓存,getMethodNoSuper_nolock从当前类的methodlist中查找method
  7. 如果找到跳转done,将找到的imp进行缓存插入,再返回imp
  8. 如果未找到,if (slowpath((curClass = curClass->getSuperclass()) == nil))这里将curClass指向父类,并判断如果父类为nil,将imp指向forward_imp,break跳出for循环;
  9. 如果父类存在,通过cache_getImp检查父类缓存是否有imp,如果impforward_imp则跳出循环,然后再检查imp,如果imp有效则跳转done流程;
  10. 查找for循环结束,没有找到imp,按LOOKUP_RESOLVER判断走动态方法决议流程resolveMethod_locked

归总分析就是,消息查找会沿着继承链一层一层往上查找,直到找到nil,如果有找到则插入缓存并返回imp,如果找不到则imp指向forward_imp也就是_objc_msgForward_impcache

上面LGTeacher调用NSObject的分类方法sayNB的过程,就是LGTeacher沿着元类的继承链找到了NSObject,并调用sayNB方法。

lookUpImpOrForward流程图

消息慢速查找流程-导出(1).png

getMethodNoSuper_nolock方法查找解析

getMethodNoSuper_nolock的方法中,查找method的算法就是findMethodInSortedMethodList方法中的二分查找算法实现的。

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);
    // method_list_t 数组是通过将sel转为uintptr_t类型的value值进行排序的
    auto first = list->begin(); // 起始位置 0
    auto base = first;
    decltype(first) probe;
    
    uintptr_t keyValue = (uintptr_t)key; // 目标sel
    uint32_t count; // 数组个数

    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1); // probe = base + floor(count / 2)

        uintptr_t probeValue = (uintptr_t)getName(probe)

        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)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }

        if (keyValue > probeValue) { // 目标value大于,二分法的中间数
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

这里的二分算法设计还是挺巧妙的,可以慢慢品玩;其中在keyValue == probeValue的时候,会进入while循环,让probe--,直到找到同名方法中最前面的方法,同名方法就是我们分类中重写的方法,分类中方法会放在前面,所以调用是会优先调用分类中的方法实现。

cache_getImp方法解析

image.png 根据之前对CacheLookup的分析,cache_getImpMissLabelDynamic传的是LGetImpMissDynamic,因此如果CacheLookup中找不到imp就会进入LGetImpMissDynamic,这里仅仅是返回了0值,所以cache_getImp流程在这里也就断了返回的是0

forward_imp解析

在取父类(curClass = curClass->getSuperclass()) == nil)的判断中,如果往上没有父类了,即条件成立,那imp会指向_objc_msgForward_impcache类型的forward_imp,并返回执行imp

image.png

  • _objc_msgForward_impcache中只是对__objc_msgForward进行调用;
  • __objc_msgForward是对__objc_forward_handler相关的方法操作返回值到x17,并通过TailCallFunctionPointer调用x17

image.png__objc_forward_handler就是函数objc_defaultForwardHandler,这里面就是直接抛出错误unrecognized selector sent to instance

这里的错误信息字符串中,对+-的处理是通过元类判断的,底层中没有+号方法或-号方法,都是OC的语法设计。

总结

  • 在缓存cache中没有找到imp,就会通过lookUpImpOrForward开启从当前类NSObject方法列表进行层层遍历,这个过程为“慢速查找”过程;
  • 如果找到了就会返回imp,并执行;
  • 如果找不到就会让imp指向forward_imp并返回,执行forward_imp系统会直接抛出方法未找到的错误;
  • 在返回forward_imp之前,还有一步resolveMethod_locked操作,这就是动态方法决议的流程,在下一篇幅展开细讲。

以上是Runtime中对objc_msgSned方法的慢速查找流程解析,如有疑问或错误之处,请在评论区留言或私信我,感谢支持~❤

❌