美文网首页
类的加载

类的加载

作者: 深圳_你要的昵称 | 来源:发表于2020-10-16 19:09 被阅读0次

前言

书接上回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<Key, Value>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 <Foundation/Foundation.h>

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的流程,这两个函数就是类的加载的核心流程,最后我们用例子验证了类的懒加载非懒加载的两种调用栈的情况。

相关文章

  • 第一章 类加载过程

    要点 类加载过程 类加载器 一、类加载过程 1.类的加载过程 类的加载 .class文件过程分为:加载---->连...

  • 深入理解jvm类加载机制

    1.什么是类加载? 类加载机制一个很大的体系,包括类加载的时机,类加载器,类加载时机。 1.1类加载过程 加载器加...

  • java基础知识之java类加载器

    1. 什么是类加载器 类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader 类加载器可以被加载...

  • 《深入理解JVM虚拟机》读书笔记-类加载器&Java模块化系统

    类加载器 一.类加载器 1.1 类与类加载器 类加载器的定义: Java虚拟机设计团队有意把 类加载阶段中 的“ ...

  • JVM类加载入门

    一 类加载顺序 class类加载-->验证-->准备--->解析--->初始化 class类加载:通过类加载器加载...

  • 学习笔记 | JAVA的反射(二)

    利用反射机制动态加载类 、获取类的方法、获取类的属性 编译时刻加载类是静态加载类,运行时加载类是动态加载类 正常创...

  • jvm类加载器详解和如何打破双亲委派机制

    类加载过程: 项目启动的时候,并不是加载项目中的所有类,是在使用的时候加载,类加载器加载类的时候首先加载父类,所以...

  • JVM - ClassLoader

    1. 概述 类加载器实际定义了类的namespace。 2.类加载方式之当前类加载器和指定类加载器 类的加载只有两...

  • java-类加载机制

    类的加载机制 主要关注点: 什么是类的加载 类的生命周期 类加载器 双亲委派模型 什么是类的加载 类的加载指的是将...

  • java类加载器及其原理

    java类加载器 : java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器(也叫应用类加载器) ...

网友评论

      本文标题:类的加载

      本文链接:https://www.haomeiwen.com/subject/yblopktx.html