【类的加载】-(1)类的启动


本页所使用的objc runtime 756.2,来自 Apple 开源文档

类的加载探寻系列:
1、【类的加载】-(1)类的启动

2、【类的加载】-(2)懒加载类与分类

3、【类的加载】-(3)loading_images

1、objc_init 流程

类的加载,初始化来自dyld 中的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();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
序号 源代码 解释
1 environ_init() 环境变量、帮助文档配置
2 tls_init() 线程key 的绑定
3 static_init C++ 静态构造函数
4 lock_init() 锁配置
5 exception_init() 异常初始化
6 _dyld_objc_notify_register Dyld 通知注册

1.1 environ_init

在函数void environ_init(void) 里,读取环境变量 env 以及帮助注释 help

相关代码如下:

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

运行代码,在控制台可以得到环境变量参数表:

objc[27076]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[27076]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[27076]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[27076]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[27076]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[27076]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[27076]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[27076]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[27076]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[27076]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[27076]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[27076]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[27076]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[27076]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[27076]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[27076]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[27076]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[27076]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[27076]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[27076]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[27076]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[27076]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[27076]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[27076]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[27076]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[27076]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[27076]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[27076]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[27076]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[27076]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[27076]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[27076]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[27076]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[27076]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[27076]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[27076]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[27076]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[27076]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork

1.2 tls_init()

对线程的Key 的绑定。

这部分不展开讨论

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

1.3 static_init

实现系统级别的 C++ 静态构造函数。

相关源码

static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}

1.4 lock_init

锁的初始化,这里并没有进行实现,意味着可以进行重写,自定义一些操作。

相关源码:

/***********************************************************************
* Lock management
**********************************************************************/
mutex_t runtimeLock;
mutex_t selLock;
mutex_t cacheUpdateLock;
recursive_mutex_t loadMethodLock;

void lock_init(void)
{
}

1.5 exception_init

异常的初始化:libobjc 的异常回调系统初始化,由map_images 调用。

即注册监听回调。

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

这个c++ 函数 set_terminate 会初始化静态的 异常回调。在OC 环境下,所有的异常抛出,都会到(*old_terminate)(); 这行代码里。

相关源码如下:

static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

在业务代码里执行一个报错的方法:

  • 类声明与实现

    @interface Dog : NSObject
    - (void)bark;
    @end
    
    @implementation Dog
    
    @end
  • 使用类执行为实现的方法

    Dog *d = [Dog alloc];
    [d bark];
  • 异常抛出如下图所示:

    objc_terminate 异常抛出

1.6 _dyld_objc_notify_register(核心)

  1. objc 特有方法
  2. 注册回调。当镜像文件被映射(mapped)、未被映射(unmapped)、初始化(initialized) 使调用。
  3. 该方法由dyld 声明。
  4. 镜像文件加载完后,dyld 会回调”映射”函数,内容为一个包含objc-image-info 数据段的数组。

注册方法源码如下:

   _dyld_objc_notify_register(&map_images, load_images, unmap_image);

这里主要关注 map_images 的这个对象,其实现代码为:

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

读取镜像文件

继续探索 map_images_nolock 这个函数,读取镜像文件的关键行为

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

至此,这一部分到此就结束了,关于images 的流程,另外单独分析。

2、read_images - 读镜像文件

2.1 初次进来: 创建容器哈希表

根据源码,得知读取镜像的主要函数为read_image ,其函数具体为:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){}

读取镜像后,最先需要做的是,为这些景象里的内容找一个家——容器,也就是开辟表结构。

在源码里,这里分别通过NXCreateMapTableNXCreateHashTable开辟了2个表,主要存储以下的内容:

  • 未共享缓存的类 gdb_objc_realized_classes

    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
  • 开辟内存空间类 - allocatedClasses

    allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);

2.2 类的处理

从编译后的类列表中,取出所有的类,并进行下一步操作。

for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
}
  • 取出系统类

    Class cls = (Class)classlist[i]
  • 读取类

    Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
  • 懒加载相关,条件为 newCls != cls && newCls, 即两个类不相等时

    1. 初始化懒加载所需内存空间

      resolvedFutureClasses = (Class *)
                          realloc(resolvedFutureClasses, 
                                  (resolvedFutureClassCount+1) * sizeof(Class));
    2. 懒加载的类添加到数组中来

      resolvedFutureClasses[resolvedFutureClassCount++] = newCls;

2.3 方法编号处理

把未注册的方法名进行注册到一张HASH表里,形成方法名与方法的映射

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

            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                // 注册SEL的操作
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }

注册到的表名为namedSelectors, 具体的实现在这里

NXMapInsert(namedSelectors, sel_getName(result), result);

2.4 协议处理

找到类遵守的协议,修复协议的引用。

关键字: readProtocol 、remapProtocolRef

相关源码,如下所示:

找到协议:

for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        NXMapTable *protocol_map = protocols();
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map,
                         isPreoptimized, isBundle);
        }
    }

修复协议引用(重映射):

for (EACH_HEADER) {
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
}

2.5 非懒加载处理

初始化所有非懒加载类,进行 rw ro 操作

for (EACH_HEADER) {
      classref_t *classlist =
          _getObjc2NonlazyClassList(hi, &count);
      addClassTableEntry(cls);
      realizeClassWithoutSwift(cls);
}

2.6 待处理的类

找到最新特征的类,以备CoreFoundation 需要操作它

if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
}

2.7 分类处理

对非类进行处理,包括Class 的和metaClass

for (EACH_HEADER) {
       category_t **catlist =
           _getObjc2CategoryList(hi, &count);
       bool hasClassProperties = hi->info()->hasCategoryClassProperties();
       for (i = 0; i < count; i++) {
           category_t *cat = catlist[i];
           Class cls = remapClass(cat->cls);
       }
}

3、类方法重点

3.1 类的读取readClasses

3.1.1 判断是否是未来处理的类popFutureNamedClass

如果是,就读取data,设置rw 和 ro

if (Class newCls = popFutureNamedClass(mangledName)) {
        // This name was previously allocated as a future class.
        // Copy objc_class to future class's struct.
        // Preserve future's rw data block.

        if (newCls->isAnySwift()) {
            _objc_fatal("Can't complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        // 在这里,读取data(), 并进行设置 rw /ro
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);

        addRemappedClass(cls, newCls);

        replacing = cls;
        cls = newCls;
    }

3.1.2 添加命名过的类

addNamedClass(cls, mangledName, replacing);

3.1.3 把类插入到包含所有类的表里

addClassTableEntry(cls);

具体的实现为如下:

static void addClassTableEntry(Class cls, bool addMeta = true) {
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    assert(!NXHashMember(allocatedClasses, cls));

    if (!isKnownClass(cls))
        NXHashInsert(allocatedClasses, cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

3.2 realizeClassWithoutSwift

实现类的初次初始化,包括开辟rw数据的内存空间。

3.2.1 取出类里的 ro 部分(data)

ro = (const class_ro_t *)cls->data();

3.2.2 开辟rw 内存空间

// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);

最终通过cls->setData(rw) , 将rw 作为data赋值给类 cls。

但是到这里,cls 仅仅是对数据部分做了初始化,里面的方法都没有写入。

如何验证?

通过lldb 来进行验证吧:

  1. 通过p/x 获取cls指针
  2. 偏移16位获得bits 的指针
  3. 将bits 强转为class_data_bits_t 的类型$2
  4. 读取$2点data() 方法。

过程可见下图,methods 属性下的list 为空,并没有方法列表的写入。

通过lldb 得到的rw 部分

可见该过程,仅仅对clsrw 的属性的roflags进行了赋值,而rw 并没有得到赋值

3.2.3 实现父类和元类关联

3.2.3.1 子类链接父类

上面的流程都是在初始化类的各种属性后,而类的结构最重要的一点是它与父类的关联,以及与元类的isa 指向。

所以下面的操作是

  • 通过remapClass(cls->superclass)对父类进行映射查找
  • 通过remapClass(cls->ISA)对元类进行查找

然后通过realizeClassWithoutSwift 进行递归,逐步得到相应的supercls以及metacls 的指针。

步骤依然是:类——元类——根元类——NSObject——nil(跳出)

supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

这两个失踪人口进行搜寻成功后,就可以将其指针对类进行关联

// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
3.2.3.2 父类链接子类

如果找到父类,在父类添加双向链表的引用,将子类引用添加到父类的子类列表里,最终实现父类和子类建立关联

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

结论是:子类里添加父类链接,父类里也添加子类链接,你中有我,我中有你。

子类——父类关联图

接下来看一下父类链接的具体实现:

static void addSubclass(Class supercls, Class subcls)
{
    runtimeLock.assertLocked();

    if (supercls  &&  subcls) {
        assert(supercls->isRealized());
        assert(subcls->isRealized());
        subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
        supercls->data()->firstSubclass = subcls;

        if (supercls->hasCxxCtor()) {
            subcls->setHasCxxCtor();
        }

        if (supercls->hasCxxDtor()) {
            subcls->setHasCxxDtor();
        }

        if (supercls->hasCustomRR()) {
            subcls->setHasCustomRR(true);
        }

        if (supercls->hasCustomAWZ()) {
            subcls->setHasCustomAWZ(true);
        }

        // Special case: instancesRequireRawIsa does not propagate 
        // from root class to root metaclass
        if (supercls->instancesRequireRawIsa()  &&  supercls->superclass) {
            subcls->setInstancesRequireRawIsa(true);
        }
    }
}

实现为:

父类的第一个子类,赋值给子类的下一个兄弟类:

subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;

子类赋值给父类的第一个子类:

supercls->data()->firstSubclass = subcls;

同时,子类继承父类的各种能力

  • hasCxxCtor
  • hasCxxDtor
  • hasCustomRR
  • hasCustomAWZ
  • instancesRequireRawIsa(适用NONPOINTER_ISA)

3.2.4 methodizeClass - rw 内容的填充

  1. 对之前创建的rw 进行方法、属性,协议的填充实现

  2. 对分类的实现

  • 方法的填充实现

        method_list_t *list = ro->baseMethods();
        if (list) {
            prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
            rw->methods.attachLists(&list, 1);
        }
  • 属性填充实现

        property_list_t *proplist = ro->baseProperties;
        if (proplist) {
            rw->properties.attachLists(&proplist, 1);
        }
  • 协议填充实现

        protocol_list_t *protolist = ro->baseProtocols;
        if (protolist) {
            rw->protocols.attachLists(&protolist, 1);
        }
  • 根元类额外的实现

    if (cls->isRootMetaclass()) {
            // root metaclass
            addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
        }
  • 添加分类

        // Attach categories.
        category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
        attachCategories(cls, cats, false /*don't flush caches*/);

提一下上面的方法实现中添加的机制,究竟是如何的?继续看源码,可以看到添加情况有3种

  • 多对多 - 方法原已有多个,在需要添加多个

            if (hasArray()) {
                // many lists -> many lists
                uint32_t oldCount = array()->count;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
                array()->count = newCount;
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }

    这里经历了3个步骤:

    • 获取原方法数: array()->count
    • 获取原方法数+新方法的总方法数: oldCount + addedCount
    • 内存扩容:realloc
    • 字节移动:内存中原方法平移:memmove
    • 字节移动:新方法拷贝进入:memcpy
  • 0 对多 - 方法原没有,在需要添加多个

    list = addedLists[0];
  • 1 对多

    // 1 list -> many lists
    List* oldList = list;
    uint32_t oldCount = oldList ? 1 : 0;
    uint32_t newCount = oldCount + addedCount;
    setArray((array_t *)malloc(array_t::byteSize(newCount)));
    array()->count = newCount;
    if (oldList) array()->lists[addedCount] = oldList;
    memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
    

另外,可以观察到无论是方法methods、属性properties、协议protocols,在添加时,使用的同样的方法attachLists, 这是因为作为同为class_rw_t类型下的属性,他们拥有同样的数据结构:二维数组。

其中方法列表和属性列表的根类型,均为entsize_list_tt

方法列表结构:

class method_array_t : 
    public list_array_tt<method_t, method_list_t>

属性列表结构:

class property_array_t : public list_array_tt<property_t, property_list_t>

协议列表结构:

class protocol_array_t : 
    public list_array_tt<protocol_ref_t, protocol_list_t>

方法列表设计成二维数组,方便分类方法的批量插入:

method_array_t 结构图

4、小结

类的加载,流程分为环境的启动配置、加载镜像、读取类方法、对类内存空间初始化、子类-父类建立连接,以及最终rw填充方法,并对分类方法加入进行实现。


文章作者: 李佳
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 李佳 !
评论
 上一篇
【底层探索】- runtime面试题集 【底层探索】- runtime面试题集
〇、引言前面一步步学习了对象、类、方法、类的加载等,这些其实都是Runtime 的基础,而Runtime 又是iOS开发语言 Objective C 的精髓,因此关于Runtime 的面试题举不胜举。 下面就简单的介绍几个,并提供解题思路,
2020-03-30 李佳
下一篇 
【底层探索】dyld浅析 【底层探索】dyld浅析
本页所使用的objc runtime 756.2,来自GITHUB 一、引言前文研究了对象、方法的基础,知道了对象/类的结构,类方法的生成和传递原理。下面该进入到核心环节,就是类的加载了,在这一个模块,需要了解的有下面几点: OC 的
2020-03-23 李佳
  目录