类的加载

前言

书接上回dyld & objc的关联,我们知道了系统在objc库的_objc_init函数中注册了关于镜像文件读取、加载和移除的回调函数,然后在dyld链接的过程去触发这些回调,告知objc库去加载类信息等一系列操作。今天我们大致分析下类的加载的具体流程。

1 _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();
    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,这就是之前提的关于镜像文件读取、加载和移除的三个回调函数。
先看看初始化流程,大致做了哪些工作:

1.1 environ_init - 环境变量的初始化



举例说明:

  1. 打开项目Edit Scheme...

2.设置环境变量,例如OBJC_PRINT_LOAD_METHODS = YES

3.run项目

一些环境变量设置:

环境变量名 说明
OBJC_PRINT_OPTIONS 输出OBJC已设置的选项
OBJC_PRINT_IMAGES 输出已load的image信息
OBJC_PRINT_LOAD_METHODS 打印 Class 及 Category 的 + (void)load 方法的调用信息
OBJC_PRINT_INITIALIZE_METHODS 打印 Class 的 + (void)initialize 的调用信息
OBJC_PRINT_RESOLVED_METHODS 打印通过 +resolveClassMethod: 或 +resolveInstanceMethod: 生成的类方法
OBJC_PRINT_CLASS_SETUP 打印 Class 及 Category 的设置过程
OBJC_PRINT_PROTOCOL_SETUP 打印 Protocol 的设置过程
OBJC_PRINT_IVAR_SETUP 打印 Ivar 的设置过程
OBJC_PRINT_VTABLE_SETUP 打印 vtable 的设置过程
OBJC_PRINT_VTABLE_IMAGES 打印 vtable 被覆盖的方法
OBJC_PRINT_CACHE_SETUP 打印方法缓存的设置过程
OBJC_PRINT_FUTURE_CLASSES 打印从 CFType 无缝转换到 NSObject 将要使用的类(如 CFArrayRef 到 NSArray * )
OBJC_PRINT_GC 打印一些垃圾回收操作
OBJC_PRINT_PREOPTIMIZATION 打印 dyld 共享缓存优化前的问候语
OBJC_PRINT_CXX_CTORS 打印类实例中的 C++ 对象的构造与析构调用
OBJC_PRINT_EXCEPTIONS 打印异常处理
OBJC_PRINT_EXCEPTION_THROW 打印所有异常抛出时的 Backtrace
OBJC_PRINT_ALT_HANDLERS 打印 alt 操作异常处理
OBJC_PRINT_REPLACED_METHODS 打印被 Category 替换的方法
OBJC_PRINT_DEPRECATION_WARNINGS 打印所有过时的方法调用
OBJC_PRINT_POOL_HIGHWATER 打印 autoreleasepool 高水位警告
OBJC_PRINT_CUSTOM_RR 打印含有未优化的自定义 retain/release 方法的类
OBJC_PRINT_CUSTOM_AWZ 打印含有未优化的自定义 allocWithZone 方法的类
OBJC_PRINT_RAW_ISA 打印需要访问原始 isa 指针的类
OBJC_DEBUG_UNLOAD 卸载有不良行为的 Bundle 时打印警告
OBJC_DEBUG_FRAGILE_SUPERCLASSES 当子类可能被对父类的修改破坏时打印警告
OBJC_DEBUG_FINALIZERS 警告实现了 -dealloc 却没有实现 -finalize 的类
OBJC_DEBUG_NIL_SYNC 警告 @synchronized(nil) 调用,这种情况不会加锁
OBJC_DEBUG_NONFRAGILE_IVARS 打印突发地重新布置 non-fragile ivars 的行为
OBJC_DEBUG_ALT_HANDLERS 记录更多的 alt 操作错误信息
OBJC_DEBUG_MISSING_POOLS 警告没有 pool 的情况下使用 autorelease,可能内存泄漏
OBJC_DEBUG_DUPLICATE_CLASSES 当出现类重名时停机
OBJC_USE_INTERNAL_ZONE 在一个专用的 malloc 区分配运行时数据
OBJC_DISABLE_GC 强行关闭自动垃圾回收,即使可执行文件需要垃圾回收
OBJC_DISABLE_VTABLES 关闭 vtable 分发
OBJC_DISABLE_PREOPTIMIZATION 关闭 dyld 共享缓存优化前的问候语
OBJC_DISABLE_TAGGED_POINTERS 关闭 NSNumber 等的 tagged pointer 优化
OBJC_DISABLE_NONPOINTER_ISA 关闭 non-pointer isa 字段的访问

1.2 tls_init - 线程相关初始化

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
}

pthread线程的key值处理。

1.3 static_init - 系统的C++ 静态构造函数初始化

1.4 runtime_init

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}



我们发现unattachedCategoriesallocatedClasses都是继承ExplicitInit,并且是一个集合类DenseMap是在llvm中用的非常广泛的数据结构,它本身的实现是一个基于Quadratic probing(二次探查)的散列表。
由此可见,runtime_init其实就是给objc的类unattachedCategoriesallocatedClasses进行初始化,各自生成一个散列表。

1.5 exception_init - libobjc的异常处理系统初始化

1.6 cache_init - cache缓存的初始化


这个task是mach_task_self()

由上图可见,mach_task_self()实际上是mach上的一个端口__darwin_mach_port_t,Mach端口是Mac OS X中进程通信的一种方式,那么缓存cache的初始化的实质就是在mach上的一个端口的指定过程。

1.7 _imp_implementationWithBlock_init - 启动回调机制

该方法主要是启动回调机制,通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载libobjc-trampolines.dylib。

1.8 _dyld_objc_notify_register

这个上回dyld & objc的关联已经分析了,它是dyld链接库时的所需回调函数的注册。dyld通过调用这些回调,通知objc去加载镜像文件,其中就包含了类的加载,也是我们今天要重点分析的对象。

2. 类的加载流程

首先看看3个回调函数,哪个是关联到类的加载

2.1 map_images

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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

2.2 load_images


上图可见,load_images与类加载无关。

2.3 unmap_image


这个也与类加载无关。
最终我们将目标锁定在函数map_images_nolock

2.4 map_images_nolock





2.5 _read_images

接着我们来到_read_images





通过对_read_images源码的大致分析,总共有以下流程:

  1. 条件控制,首次进来_read_images时的一些处理
  2. 修复@selector的混乱问题
  3. 针对错误和混乱的类的处理
  4. 修复一些未加载的类
  5. 修复一些消息
  6. 加载协议
  7. 修复未被加载的协议
  8. 分类的处理
  9. 类的加载处理
  10. 被入侵的类的处理
  11. 实现所有类
  12. 打印一些信息

3. 与类加载相关的流程

我们对_read_images函数流程的大致分析,可以发现,discover classesrealize non-lazy classes是和类的加载最相关的。

3.1 discover classes

    // 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;
        }

        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];
            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");

很明显,在Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);做了读取类的信息。

3.2 realize non-lazy classes

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &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
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

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

关键代码是:realizeClassWithoutSwift(cls, nil);-->实现类的相关操作。

3.3 readClass



再看看addNamedClass

然后是addClassTableEntry

3.4 realizeClassWithoutSwift




3.5 methodizeClass


3.6 示例验证以上流程

#import 

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;

- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;

+ (void)kc_sayClassMethod;

@end

NS_ASSUME_NONNULL_END



#import "LGPerson.h"

@implementation LGPerson

//+ (void)load{
//    
//}

- (void)kc_instanceMethod2{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod1{
    NSLog(@"%s",__func__);
}

- (void)kc_instanceMethod3{
    NSLog(@"%s",__func__);
}

+ (void)kc_sayClassMethod{
    NSLog(@"%s",__func__);
}


@end


在main.m中调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person kc_instanceMethod1];
        NSLog(@"%p",person);
    }
    return 0;
}

readClass中设置断点


realizeClassWithoutSwift中设置断点

run运行Demo




根据调用栈信息,reaizeClassWithoutSwift的调用时机是在消息发送的慢速查找lookUpImpOrForward中,
消息发送之前会先判断类是否实现,cls = initializeAndLeaveLocked(cls, inst, runtimeLock)----这是OC中著名的懒加载机制,将类的加载推迟到第一次方法调用的时候。
懒加载的调用栈是:
[LGPerson alloc] --> objc_alloc -->callAlloc --> _objc_msgSend_uncached -->lookUpImpOrForward -->initializeAndLeaveLocked-->initializeAndMaybeRelock-->realizeClassMaybeSwiftAndUnlock-->realizeClassMaybeSwiftMaybeRelock --> realizeClassWithoutSwift

那非懒加载呢?有什么方式可以提前加载LGPerson类?之前我们分析过load方法是优先加载的,我们可以再LGPerson里实现+load方法,再断点看看:


非懒加载调用栈是:
_dyld_start --> _objc_init --> _dyld_objc_notify_register --> dyld::registerObjcNotifiers --> dyld::notityBatchPartial --> map_images -->map_images_nolock --> _read_images --> realizeClassWithoutSwift

总结流程

我们通过对_objc_init函数流程的分析,找到了_dyld_objc_notify_register回调函数的注册,然后通过对3个回调函数的大致流程的分析,锁定到类的加载与函数map_images相关,进而查看其源码,走到了_read_images,大致分析了_read_images的流程,其中discover classesrealize non-lazy classes均与类的加载有关联,然后分析了readClassrealizeClassWithoutSwift的流程,这两个函数就是类的加载的核心流程,最后我们用例子验证了类的懒加载非懒加载的两种调用栈的情况。

你可能感兴趣的:(类的加载)