类的加载

268 阅读7分钟

应用程序在启动的过程中,经过dyld一系列的操作,最后进入Objc的源码,入口就是_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();
    //全局静态C++函数调用
    static_init();
    //初始化两张表
    runtime_init();
    //异常捕捉初始化
    exception_init();

#if __OBJC2__
    //缓存
    cache_t::init();
#endif

    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__

    didCallDyldNotifyRegister = true;

#endif

}

environ_init()

这个函数里,第一方法就是环境变量的初始化,平时我们都添加过环境变量,就是在这个初始化的。我们使用环境变量

  1. 在源码工程中打印环境变量
打印加载的image和library
OBJC_PRINT_IMAGES: log image and library names as they are loaded 
测量image加载步骤的持续时间
OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps  
OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods  打印所有+load方法的调用
OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods   打印所有+initialize方法的调用
OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:    打印由+resolveClassMethod:和+resolveInstanceMethod:创建的方法
OBJC_PRINT_CLASS_SETUP: log progress of class and category setup  打印类和分类的加载
OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup   打印协议的加载
OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
OBJC_PRINT_CACHE_SETUP: log processing of method caches
OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
OBJC_PRINT_EXCEPTIONS: log exception handling
OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated
OBJC_DISABLE_VTABLES: disable vtable dispatch
OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.   不使用tagged pointer
OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields  不使用non_pointer isa
OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
OBJC_DISABLE_FAULTS: disable os faults
OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
  1. 使用Xcode打印出环境变量

image.png

runtime_init()

初始化两张表

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();  //用来保存 已经加载的类的表
}

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

map_images load_images注册到dyld,由dyld调用方法。

  1. map_images 管理文件中和动态库中所有的符号(class,protocol,selector,category)
  2. 执行load方法

_read_images

map_images->map_images_nolock->_read_images进入到加载image的关键函数。

_read_image主体流程

当我们把源码中的判断都收起来,每一块上面有一段注释,下面有一个log,每一块代码的作用就很清晰了,整体流程就比较明朗。部分代码截图:

image.png

  1. 条件控制进行一次的加载
  2. 修复预编译阶段的 @selector 的混乱问题
  3. 错误混乱的类处理 - 处理类
  4. 修复重映射一些没有被镜像文件加载进来的 类
  5. 修复一些消息
  6. 当我们类里面有协议的时候 : readProtocol
  7. 修复没有被加载的协议
  8. 分类处理
  9. 类的加载处理
  10. 没有被处理的类 优化那些被侵犯的类

!doneOnce 第一次加载

initializeTaggedPointerObfuscator(); 初始化tagged pointer
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor

int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

这里又创建了一张表,这张表是包含所有的类,无论是加载的还是没加载的。3/4是苹果常用的负载因子,用来做扩容的。这里计算了所有类的大小,然后乘以4/3,设置成表的大小,这样这张表就可以在扩容的情况正好保存所有的类。

readClass

这个方法中,我们只研究自定义的类,系统的类可能会有特殊的加载方式,所以我们判断加载自己的类的时候,进入断点,然后一步一步往下走。下面是省略掉部分源码的代码

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName();
    const char *person = "SwwPerson";
    if (strcmp(mangledName, person) == 0) {
        printf("自己的类");
    }

    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    

    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }


    return cls;

}

这个方法的主要作用是两个函数addNamedClass(cls, mangledName, replacing)addClassTableEntry(cls)

  1. addNamedClass(cls, mangledName, replacing)本来类只是一个地址,没有名字,这个方法把类名付给了类。

  2. 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.

    auto &set = objc::allocatedClasses.get();

    ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

把类添加到初始化类的表中,元类也添加到表中。

realizeClassWithoutSwift

在把类添加名字,并且存入已初始化类的表中之后。需要真正的初始化这个类。之前谈到过类的结构,主要包括isasuperclasscachebits。在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 = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // 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 {
    
        //正常的类进入这里,ro复制给rw
        // 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);
    }

    //创建一个空的cache给cls
    cls->cache.initializeToEmptyOrPreoptimizedInDisguise();


    cls->chooseClassArrayIndex();


    
    //父类和元类递归初始化
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);



    // Update superclass and metaclass in case of remapping

    //设置父类,isa走位
    cls->setSuperclass(supercls);
    //设置元类
    cls->initClassIsa(metacls);
   


    // Connect this class to its superclass's subclass lists

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }


    //处理类的方法列表,分类等
    // Attach categories
    methodizeClass(cls, previously);


    return cls;

}

懒加载类和非懒加载类

我们平时写代码的时候也使用过懒加载的方式,这个样既能节约内存,也能提高效率。苹果在设计类的加载的过程中,也使用了懒加载和非懒加载,他们的区别就是+load

  1. 非懒加载类:实现了+load方法就是非懒加载类。在main函数之前加载。
  2. 懒加载类:在第一次给这个类发送消息的时候初始化的。

image.png

类加载的流程总结

程序运行之前,会链接很多的image,通过read_images这个方法,把images都传进来。在加载类的过程中,分别对每一个image以及包含的每一个class加载,是遍历加载的。

image.png

未命名文件(2).png

类 和 分类

我们已经知道+load方法会使类变成非懒加载类,那么分类中使用+load会有什么影响呢,我们分为四种情况分别探索。

  1. 主类使用+load 分类使用+load

image.png

image.png

会调用attachCategories,ro里面只有主类的数据。方法列表是个二维数组[[分类A方法],[分类B方法],[主类方法]]

  1. 主类使用+load 分类不使用

image.png

image.png

类为非懒加载类。不调用attachCategories。主类里的6个方法和分类里的2个方法都从mach-O中加载到ro。分类方法在前,主类方法在后。

  1. 主类不使用 分类使用+load

情况同2.

  1. 类和分类都不使用+load

image.png

image.png

类为懒加载类,当第一次收到消息时加载。主类里的6个方法和分类里的2个方法都从mach-O中加载到ro。分类方法在前,主类方法在后。