0%

本节我们主要了解一些多线程的理论知识。

进程和线程

定义

进程
  • 进程是指在系统中正在运行的一个应用程序。
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内。
  • 通过“活动监视器”可以查看 Mac 系统中所开启的进程。
线程
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。
  • 进程要想执行任务,必须得有线程,进程至少要有一条线程。
  • 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。

关系

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。

补充

  1. 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  2. 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进 程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
  3. 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  4. 线程是处理器调度的基本单位,但是进程不是。
  5. 线程没有地址空间,线程包含在进程地址空间中。

多线程原理

  • 对于单核CPU同一时间,CPU只能处理一条线程,即只有一条线程在工作,
  • iOS中的多线程同时执行的本质是CPU在多个任务直接进行快速的切换,由于CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果。其中切换的时间间隔就是时间片

多线程的优点

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU,内存)
  • 线程上的任务执行完成后,线程会自动销毁

多线程的缺点

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB,90ms的时间)。
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能。
  • 线程越多,CPU 在调用线程上的开销就越大。
  • 程序设计更加复杂,比如线程间的通信,多线程的数据共享。

多线程生命周期

多线程的生命周期主要分为5部分:新建 - 就绪 - 运行 - 阻塞 - 死亡,如下图所示

多线程生命周期

  • 新建:主要是实例化线程对象
  • 就绪:线程对象调用start方法,将线程对象加入可调度线程池,等待CPU的调用,即调用start方法,并不会立即执行,进入就绪状态,需要等待一段时间,经CPU时间片调度后再执行,也就是从就绪状态进入运行状态
  • 阻塞:当满足某个预定条件时,可以使用休眠,即sleep,或者同步锁,阻塞线程执行。当进入sleep时,会重新将线程加入就绪中。
  • 死亡:分为两种情况,
    1. 正常死亡,即线程执行完毕
    2. 非正常死亡,即当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)
  • 运行:就是线程执行,处于运行中的线程拥有一段可以执行的时间(时间片)。
    1. 如果时间片用尽,线程就会进入就绪状态队列
    2. 如果时间片没有用尽,且需要开始等待某事件,就会进入阻塞状态队列
    3. 等待事件发生后,线程又会重新进入就绪状态队列
    4. 每当一个线程离开运行,即执行完毕或者强制退出后,会重新从就绪状态队列中选择一个线程继续执行

线程池

线程池流程图如下:

线程池

  1. 判断核心线程池是否都正在执行任务
    • 返回NO,创建新的工作线程去执行
    • 返回YES,进入2
  2. 判断线程池工作队列是否已经饱满
    • 返回NO,将任务存储到工作队列,等待CPU调度
    • 返回YES,进入3
  3. 判断线程池中的线程是否都处于执行状态
    • 返回NO,安排可调度线程池中空闲的线程去执行任务
    • 返回YES,进入4
  4. 交给饱和策略去执行。饱和策略有一下四种:
    • AbortPolicy:直接抛出RejectedExecutionExeception异常来阻止系统正常运行
    • CallerRunsPolicy:将任务回退到调用者
    • DisOldestPolicy:丢掉等待最久的任务
    • DisCardPolicy:直接丢弃任务
    • 这四种拒绝策略均实现的RejectedExecutionHandler接口

互斥锁与自旋锁

锁是用户保护临界区,确保同一时间,只有一条线程能够访问临界区。

自旋锁

  • 自旋锁在获取到资源之前一直处于忙等。
  • 自旋锁的使用场景:锁持有时间短,且线程不希望在重新调用上花太多成本。OC属性的关键字atomic就是使用了自旋锁。
  • 使用自旋锁,当心线程访问代码时,如果发现其他线程转给你在锁定代码,新线程会用死循环的方法,一直等待锁定的代码执行完成,比较消耗性能。

互斥锁

  • 互斥锁在获取到资源之前是休眠状态,释放资源后会被唤醒。

  • 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象

  • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差

  • 能够加锁的任意 NSObject 对象

  • 锁对象一定要保证所有的线程都能够访问

    几个多线程题目

任务执行的影响因素

  • cpu的调度
  • 执行任务的复杂度
  • 任务的优先级
  • 线程的状态

优先级反转

两种线程:IO密集型、CPU密集型

  • IO密集型,频繁等待的线程。更容易得到优先级提升。
  • CPU密集型,很少等待的线程。
  • IO密集型线程容易饿死。
  • cpu调度来提升等待线程的优先级

优先级的影响因素

  • 用户指定。
  • 等待的频繁度。
  • 长时间不执行。

KVO是一种机制,它允许对象在其他对象的指定属性发生更改时收到通知。它最常用的一个场景就是viewconroller中监听model属性的变化从而刷新页面展示。

KVO使用过程的细节

基本使用

KVO的基本使用就是三部曲:

  • 注册观察者 addObserver:forKeyPath:options:context

    1
    [self.person addObserver:self forKeyPath:@"nickname" options:NSKeyValueObservingOptionNew context:NULL];
  • 实现KVO的回调observeValueForKeyPath:ofObject:change:context

    1
    2
    3
    4
    5
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"nickname"]) {
    NSLog(@"%@",change);
    }
    }
  • 移除观察者removeObserver:forKeyPath:context

    1
    [self.person removeObserver:self forKeyPath:@"nickname" context:NULL];

context参数的使用

基本使用的过程中我们经常会给contextNULL作为实参,context参数的作用很容易被我们忽略。苹果官方文档对context有详细的说明

The context pointer in the addObserver:forKeyPath:options:context: message contains arbitrary data that will be passed back to the observer in the corresponding change notifications. You may specify NULL and rely entirely on the key path string to determine the origin of a change notification, but this approach may cause problems for an object whose superclass is also observing the same key path for different reasons.

A safer and more extensible approach is to use the context to ensure notifications you receive are destined for your observer and not a superclass.

大体意思就是:addObserver:forKeyPath:options:context:方法中的context指针包含任意数据,这些数据将在相应的更改通知中传递回观察者。可以通过指定context为NULL,从而依靠keyPath键路径字符串传来确定更改通知的来源,但是这种方法可能会导致对象的父类由于不同的原因也观察到相同的键路径而导致问题。所以可以为每个观察到的keyPath创建一个不同的context,从而完全不需要进行字符串比较,从而可以更有效地进行通知解析。

context主要是用于区分不同对象的同名属性,从而在KVO回调方法中可以直接使用context进行区分,可以大大提升性能,以及代码的可读性。

  • 不使用context时,我们通过字符串判断:

    1
    2
    3
    4
    5
    6
    [self.person addObserver:self forKeyPath:@"nickname" options:NSKeyValueObservingOptionNew context:NULL];
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"nickname"]) {
    NSLog(@"%@",change);
    }
    }
  • 使用context时,我们通过context判断:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //context定义
    static void *PersonNickNameContext = &PersonNickNameContext;
    static void *PersonNameContext = &PersonNameContext;
    //注册观察者
    [self.person addObserver:self forKeyPath:@"nick" options:NSKeyValueObservingOptionNew context:PersonNickNameContext];
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
    //KVO的回调
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if (context == PersonNickNameContext) {
    NSLog(@"%@",change);
    }else if (context == PersonNameContext){
    NSLog(@"%@",change);
    }
    }

是否有必要移除KVOobserver

官方文档对removeObserver也有说明:

When removing an observer, keep several points in mind:

  • Asking to be removed as an observer if not already registered as one results in an NSRangeException. You either call removeObserver:forKeyPath:context: exactly once for the corresponding call to addObserver:forKeyPath:options:context:, or if that is not feasible in your app, place the removeObserver:forKeyPath:context: call inside a try/catch block to process the potential exception.
  • An observer does not automatically remove itself when deallocated. The observed object continues to send notifications, oblivious to the state of the observer. However, a change notification, like any other message, sent to a released object, triggers a memory access exception. You therefore ensure that observers remove themselves before disappearing from memory.
  • The protocol offers no way to ask an object if it is an observer or being observed. Construct your code to avoid release related errors. A typical pattern is to register as an observer during the observer’s initialization (for example in init or viewDidLoad) and unregister during deallocation (usually in dealloc), ensuring properly paired and ordered add and remove messages, and that the observer is unregistered before it is freed from memory.

翻译过来就是,移除观察者时,注意以下几点:

  • 如果未注册为观察者,在移除观察者的时候会导致NSRangeException异常。removeObserver必须和addObserver对应,且只能调用一次。如果项目中不能保证,就需要在使用的时候使用try/catch来处理异常。
  • 观察者在对象销毁的时候不会自动移除观察者。被观察者会继续发送通知,对观察者来说这个状态是感知不到的。但是,向一个已经释放的对象发送通知会引起内存访问异常。所以,我们要保证观察者在内存释放之前移除观察。
  • 这个协议没有方法可以判断他是一个观察者还是被观察者,写代码是要避免释放内存相关的错误。一个典型的规范就是在观察者初始画的时候注册观察,在dealloc的时候移除观察,以确保成对和有序地添加和删除消息,并确保观察者在注册之前被取消注册,从内存中释放出来。

所以,总的来说,KVO注册观察者 和移除观察者是需要成对出现的,如果只注册,不移除,会出现野指针的崩溃

自动触发与手动触发

KVO观察的自动和手动两种方式

  • 自动开关,automaticallyNotifiesObserversForKey返回YES的时候标示自动监听,如果是NO表示我们需要手动监听

    1
    2
    3
    4
    // 自动开关
    + (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    return YES;
    }
  • 如果是手动,我们需要通过手动开关监听

    1
    2
    3
    4
    5
    6
    - (void)setNickName:(NSString *)nickName{
    //手动开关
    [self willChangeValueForKey:@"nickName"];
    _nickName = namenickName
    [self didChangeValueForKey:@"nickName"];
    }

观察多个属性变化

我们以观察两个属性为例,例如我们需要根据速度speed和时间time,取得当前的路程distance。我们用两种方式。

  • 第一种就是分别观察速度speed和时间time两个属性,当其中一个发生变化计算 当前路程distance

  • 第二种方式就是,通过keyPathsForValuesAffectingValueForKey方法,将两个观察合为一个观察,即观察当前路程distance

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //1、合二为一的观察方法
    + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"distance"]) {
    NSArray *affectingKeys = @[@"speed", @"time"];
    keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
    }

    //2、注册KVO观察
    [self.person addObserver:self forKeyPath:@"distance" options:(NSKeyValueObservingOptionNew) context:NULL];

    //3、触发属性值变化
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.speed += 10;
    self.person.time += 1;
    }

    //4、移除观察者
    - (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"distance"];
    }

可变数组的观察

KVO是基于KVC基础之上的,所以可变数组如果直接添加数据,是不会调用setter方法的,所有对可变数组的KVO观察下面这种方式不生效的,即直接通过[self.person.dateArray addObject:@"1"];向数组添加元素,是不会触发kvo通知回调的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1、注册可变数组KVO观察者
self.person.dateArray = [NSMutableArray arrayWithCapacity:1];
[self.person addObserver:self forKeyPath:@"dateArray" options:(NSKeyValueObservingOptionNew) context:NULL];

//2、KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}

//3、移除观察者
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"dateArray"];
}

//4、触发数组添加数据
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person.dateArray addObject:@"1"];
}

在KVC官方文档中,针对可变数组的集合类型,有如下说明,即访问集合对象需要需要通过mutableArrayValueForKey方法,这样才能将元素添加到可变数组

The protocol defines three different proxy methods for collection object access, each with a key and a key path variant:

  • mutableArrayValueForKey: and mutableArrayValueForKeyPath:

    These return a proxy object that behaves like an NSMutableArray object.

  • mutableSetValueForKey: and mutableSetValueForKeyPath:

    These return a proxy object that behaves like an NSMutableSet object.

  • mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:

    These return a proxy object that behaves like an NSMutableOrderedSet object.

我们代码这样修改:

1
2
3
4
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// KVC 集合 array
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"1"];
}

现在可变数组就可以监听到了。

KVO底层探索

苹果官方文档在Key-Value Observing Implementation Details里有提到KVO的实现:

Automatic key-value observing is implemented using a technique called isa-swizzling.

The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

大概意思就是KVO的实现使用了isa的交换。当我们添加一个observer的时候isa的指向会发生改变,是一个中间类而不是真正的类。我们不能根据isa指针确定类的成员身份,而是用哪个class方法确定。

中间类是什么

看苹果官网文档我们了解了,KVO的实现时通过修改isa指针指向了一个中间类实现的,我们使用lldb探究一下中间类是什么。

  • 添加观察者之前,我们打印实例对象person的方法是JSPerson

    1
    2
    (lldb) po object_getClassName(self.person)
    "JSPerson"
  • 添加观察者之后,我们打印实例对象person的方法是NSKVONotifying_JSPerson

    1
    2
    (lldb) po object_getClassName(self.person)
    "NSKVONotifying_JSPerson"

通过上面的调试,我们看到添加观察者值isa指向了一个名为"NSKVONotifying_JSPerson的中间类。关于这个中间类我们有几个点需要研究一下。

  1. 中间类和之前的类是父子类关系吗

    我们通过一个方法来判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #pragma mark - 遍历类以及子类
    - (void)printClasses:(Class)cls{
    // 注册类的总数
    int count = objc_getClassList(NULL, 0);
    // 创建一个数组, 其中包含给定对象
    NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
    // 获取所有已注册的类
    Class* classes = (Class*)malloc(sizeof(Class)*count);
    objc_getClassList(classes, count);
    for (int i = 0; i<count; i++) {
    if (cls == class_getSuperclass(classes[i])) {
    [mArray addObject:classes[i]];
    }
    }
    free(classes);
    NSLog(@"classes = %@", mArray);
    }

    //********以下为调用********
    [self printClasses:[JSPerson class]];

    打印结果为

    1
    2
    3
    4
    classes = (
    JSPerson,
    "NSKVONotifying_JSPerson"
    )

    通过打印结果我们可以判断中间类NSKVONotifying_JSPersonJSPerson的子类。

  2. 中间类里有什么方法。

    同样,我们定义一个方法获取NSKVONotifying_JSPerson的所有方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #pragma mark - 遍历方法-ivar-property
    - (void)printClassAllMethod:(Class)cls{
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
    Method method = methodList[i];
    SEL sel = method_getName(method);
    IMP imp = class_getMethodImplementation(cls, sel);
    NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
    }
    free(methodList);
    }

    //********以下为调用********
    [self printClassAllMethod:objc_getClass("NSKVONotifying_JSPerson")];

    打印结果

    1
    2
    3
    4
    setNickName:-0x7fff207bbb57
    class-0x7fff207ba662
    dealloc-0x7fff207ba40b
    _isKVOA-0x7fff207ba403

    我们看到一共有四个方法

    • 重写了父类的setNickName方法
    • 重写了根类的classdealloc方法
    • _isKVO方法,用来判断是否是kvo
  3. dealloc中移除观察者后,isa会指回来吗

    • 移除观察者之前,我们用lldb打印

      1
      2
      (lldb) po object_getClassName(self.person)
      "NSKVONotifying_JSPerson"
    • 移除观察者之后,我们重新打印

      1
      2
      (lldb) po object_getClassName(self.person)
      "JSPerson"

    说明的确是在移除观察的时候将isa指回来的。

  4. 移除观察后中间类会销毁吗

    我们返回前一个页面,此时添加观察者的VC已经销毁,我们打印JSPerson的子类

    1
    [self printClasses:[JSPerson class]];

    打印结果

    1
    2
    3
    4
    classes = (
    JSPerson,
    "NSKVONotifying_JSPerson"
    )

    发现子类并不会被销毁。

小结

  • 实例对象isa的指向在添加KVO观察者之后,由原有类更改为指向中间类
  • 中间类重写了观察属性的setter方法classdealloc_isKVOA方法
  • dealloc方法中,移除KVO观察者之后,实例对象isa指向由中间类更改为原有类
  • 中间类从创建后,就一直存在内存中,不会被销毁

KVC的全拼是Key-Value Coding,中文是键值编码。是由NSKeyValueCoding非正式协议的一种机制。对象可以间接地访问它们的属性。这种间接访问机制是实例变量及其相关访问器方法提供的直接访问的补充。

使用KVC

通过key取值和设置值

1
2
3
4
//直接通过Key来取值
- (nullable id)valueForKey:(NSString *)key;
//通过Key来设值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

通过keyPath(路由)取值和设置值

1
2
3
4
5
//通过KeyPath来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;

//通过KeyPath来设值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

我们平时项目中主要是通过使用的是valueForKeyvalueForKeyPath的方式取值和设值,当然还有集合类型的一些操作可以参见苹果KVC的文档查看使用。

KVC的设值过程

setValue:forKey:这个方法有一个调用顺序(基本数据类型):

  • 按顺序查找名为 set: 或 _set 的第一个访问器。如果找到,则使用输入值(或根据需要展开的值)调用它并完成。
  • 如果未找到简单访问器,并且类方法 accessInstanceVariablesDirectly 返回 YES,则按顺序查找名称类似于 _、_is 或 is 的实例变量。如果找到,直接使用输入值(或解包值)设置变量并完成。
  • 在未找到访问器或实例变量时,调用 setValue:forUndefinedKey:。默认情况下,这会引发异常,但 NSObject 的子类可能会提供特定于键的行为。

整个流程图如下(以person对象设置name属性为例):

kvc取值流程

KVC取值过程

和设值过程一样,取值过程valueForKey:也有一个调用顺序(包含集合类型):

  • 在实例中搜索找到的第一个访问器方法,其名称类似于 get、is 或 _,按该顺序。如果找到,则调用它并使用结果继续执行步骤 5。否则继续下一个步骤
  • 如果没有找到简单的访问器方法,则在实例中搜索名称与模式 countOf 和 objectInAtIndex:(对应于 NSArray 类定义的原始方法)和 AtIndexes:(对应于模式)的方法NSArray 方法 objectsAtIndexes:)。如果找到其中的第一个和至少其他两个中的一个,则创建一个集合代理对象,该对象响应所有 NSArray 方法并返回该对象。否则,继续执行步骤 3。代理对象随后将它接收到的任何 NSArray 消息转换为 countOf、objectInAtIndex: 和 AtIndexes: 消息的某种组合,并将其转换为创建它的键值编码兼容对象。如果原始对象还实现了一个可选方法,其名称类似于 get:range:,则代理对象也会在适当的时候使用它。实际上,代理对象与键值编码兼容对象一起工作允许底层属性表现得好像它是一个 NSArray,即使它不是。
  • 如果没有找到简单的访问器方法或数组访问方法组,则查找名为 countOf、enumeratorOf 和 memberOf 的三元组方法:(对应于 NSSet 类定义的原始方法)。如果找到所有三个方法,则创建一个集合代理对象,该对象响应所有 NSSet 方法并返回该对象。否则,继续执行步骤 4。这个代理对象随后将它接收到的任何 NSSet 消息转换为 countOf、enumeratorOf 和 memberOf 的某种组合:消息到创建它的对象。实际上,与键值编码兼容的对象一起工作的代理对象允许底层属性表现得好像它是一个 NSSet,即使它不是。
  • 如果没有找到简单的访问器方法或集合访问方法组,并且如果接收者的类方法accessInstanceVariables直接返回YES,则搜索名为_、_is或is的实例变量,以该顺序。如果找到,直接获取实例变量的值并进行步骤5,否则进行步骤6。
  • 如果检索到的属性值是一个对象指针,只需返回结果即可。 如果该值是 NSNumber 支持的标量类型,则将其存储在 NSNumber 实例中并返回。 如果结果是 NSNumber 不支持的标量类型,则转换为 NSValue 对象并返回。
  • 如果所有其他方法都失败,请调用 valueForUndefinedKey:。默认情况下,这会引发异常,但 NSObject 的子类可能会提供特定于键的行为。

其流程图如下:

KVC取值过程

自定义实现KVC

如果自己实现一个KVC可以参考上面的顺序,实现valueForKeysetValueForKey

设值过程

  • 判断key是否为空,为空直接返回。
  • 查找是否有 setter方法 set<Key>:_set<Key>, setIs<Key>,如果有则实现并返回。
  • 如果没找到则判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
  • 查找自己的ivar列表中是否包含实例变量 _<key>, _is<Key>,<key>,is<Key>,找到就赋值。
  • 如果都搜索不到,就抛出异常。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
- (void)js_setValue:(nullable id)value forKey:(NSString *)key{
// 空判断
if (key == nil || key.length == 0) {
return;
}

// 2: setter set<Key>: or _set<Key>,
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];

if ([self js_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self js_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self js_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}

// 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"JSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}

// 4: 间接变量
// 获取 ivar -> 遍历 containsObjct -
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}

// 5:如果找不到相关实例
@throw [NSException exceptionWithName:@"JSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

取值过程

  • 同样是判断key非空
  • 按顺序查找方法:get<Key><key>countOf<Key>objectIn<Key>AtIndex
  • 判断accessInstanceVariablesDirectly的返回值是否为YES,可以则往下走,否则抛出异常。
  • 查找自己的ivar列表中是否包含实例变量 _<key>, _is<Key>,<key>,is<Key>,找到就取值。
  • 未找到抛出异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (nullable id)js_valueForKey:(NSString *)key{

// 1:判断非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop

// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"JSUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相关实例变量进行赋值
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}

总结

本篇我们主要探索了KVC的取值设值的流程。设值过程集合类型的情况没有写,感兴趣的童鞋可以查看苹果官方文档进行探索。

oc底层探索了很多了,今天主要总结一下一些相关的面试题。

load方法什么时候调用。

这个问题很多同学应该都知道,就是main函数之前,但是main之前的哪一步执行的,可能有些同学就疑惑了,,同时还有一个方法的调用时机也会被经常问到就是initialize,我们分别讨论。

load方法

  • load方法是在应用程序加载过程中调用的,确实是在main函数之前调用。
  • 具体是_dyld_objc_notify_register方法的第二个参数load_images回调的。
  • 通过prepare_load_methods递归查找load方法添加到一个load方法的加载表loadable_classes里,注意父类会比子类先加入到表中,查找完类的load方法之后,查找分类的load也会添加到一个loadable_categories表中。
  • 最后是call_load_methods调用load方法,先从loadable_classes表里循环调用类的load方法,然后从loadable_categories表里循环调用分类的load方法。
  • 因为是顺序遍历表调用load方法的。所以load方法的调用次序是父类>本类>分类
  • 如果有多个分类都有load方法,其调用顺序会根据编译的顺序调用。编译顺序可以在Compiles Sources里调整。
  • load方法过多会影响到应用的启动速度。

initialize方法。

  • initialize方法是在第一次objc_msgSend的时候调用的,它的调用时机晚于load
  • 分类的方法是在类realize之后attachCategorys进去的,会在类的方法前面。如果分类实现了initialize方法,会优先调用分类的方法。

Runtime是什么

  • runtime是由cc++汇编实现的一套API,为oc语言加入面向对象运行时功能。
  • 运行时是指讲数据类型的确定有编译时推迟到了运行时
  • 我们写的oc代码,在程序运行过程中,最终都会转换成runtimec语言代码。

⽅法的本质,sel是什么?IMP是什么?两者之间的关系是什么

⽅法的本质

⽅法的本质是消息发送,即objc_msgSend,它的流程是:

  • 快速查找 (objc_msgSend)~ cache_t 缓存消息
  • 慢速查找~ 递归⾃⼰或⽗类 ~ lookUpImpOrForward
  • 查找不到消息: 动态⽅法解析 ~ resolveInstanceMethod
  • 消息快速转发 ~ forwardingTargetForSelector
  • 消息慢速转发 ~ methodSignatureForSelectorforwardInvocation

sel是什么

sel是⽅法编号,在read_images期间就加载进⼊了内存。它实际是objc_selector结构体。

IMP是什么

imp就是我们函数实现指针,找imp就是找函数实现的过程。

selIMP的关系

  • sel就相当于书本的⽬录title
  • imp就是书本的⻚码
  • 方法调用的时候首先根据sel找到imp最后到具体函数的实现,完成调用。

能否向编译后的类中增加实例变量?能否向运⾏时创建的类中添加实例变量?

  • 不能向编译后的得到的类中增加实例变量:

    • 我们编译好的实例变量存储的位置在ro(read only),⼀旦编译完成,内存结构就完全确定。
    • 我们可以通过分类向类中添加方法属性(通过关联对象)。
  • 可以向运行时创建的类中添加实例变量,只要类没有注册到内存还是可以添加。

    这里运行时创建的类指的是通过objc_allocateClassPair方法,创建的,在调用objc_registerClassPair方法之前是可以添加实例变量的。

[self class]和[super class]区别

先定义两个类JSPersonJSStudent,其中JSStudent继承于JSPerson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface JSPerson : NSObject

@end
#import "JSPerson.h"

@implementation JSPerson

@end

@interface JSStudent : JSPerson

@end
@implementation JSStudent

- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class],[super class]);
}
return self;
}
@end

main函数里实例化一个JSStudent对象。

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
@autoreleasepool {
JSStudent *student = [[JSStudent alloc] init];;
NSLog(@"%@",student);
}
return 0;
}

发现打印结果为JSStudent - JSStudent。这是为什么呢,我们下面分析一下。

首先,JSPersonJSStudent类都没有实现class方法,根据消息发送查找流程,会调用NSObject类的class方法,它的实现为

1
2
3
4
5
6
7
8
9
- (Class)class {
return object_getClass(self);
}
//根据isa找到类
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

class方法的作用是返回当前的类,self是调用的对象也就是student实例。

  • [self class]打印的是JSStudent很好理解,因为消息接受者就是JSStudent的实例对象,通过isa找到的就是JSStudent类。

  • [super class]打印的也是JSStudent就让人困惑了,我们打开汇编调试看一下[super class]的底层调用了什么

    WechatIMG1246

    [super class]实际调用的是objc_msgSendSuper2方法,我们在源码看一下这个方法的定义:

    1
    2
    3
    4
    // objc_msgSendSuper2() takes the current search class, not its superclass.
    OBJC_EXPORT id _Nullable
    objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);

    其实看objc_msgSendSuper2的注释就可以看出来,方法查找的是本类而不是它的父类

    继续看,super是方法的第一个参数,也就是objc_super *它的结构是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message.
    #if !defined(__cplusplus) && !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    ///old结构,我们可以忽略 !__OBJC2__使用
    __unsafe_unretained _Nonnull Class class;
    #else
    __unsafe_unretained _Nonnull Class super_class;
    #endif
    /* super_class is the first class to search */
    };

    objc_super的成员变量有receiver它是当前的调用的实例也就是student,super_class是当前实例对象的类的父类。所以说[super class]的消息接受者self还是student,所以打印的是JSStudentsuper是一个关键字。

    内存平移问题

    案例说明:JSPerson类中有一个saySomething方法,在ViewController类的viewDidLoad通过两种方式调用,详细看代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @implementation JSPerson
    - (void)saySomething{

    NSLog(@"%s",__func__);
    }
    @end
    @implementation ViewController

    - (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    JSPerson *person = [JSPerson alloc];
    [person saySomething];

    Class cls = [JSPerson class];
    void  *js = &cls;
    [(__bridge id)js saySomething];
    }
    @end

    问题是这两个调用saySomething的语句是否有问题。

    • [person saySomething]:这种方式没什么疑问,正常的方法调用。它的流程是通过person对象的isa指针找到类JSPerson,首先通过内存平移找到cache里查找,如果找不到,继续平移找到bits查找方法列表查找,最后找到imp调用。

    • [(__bridge id)js saySomething]:运行代码,我们这一行代码也正常执行了,原因是什么呢

      void *js = &cls;说明js是一个指向JSPerson类首地址的指针,它和对象的isa指向的是同一个地址,通过内存平移也可以找到对应的方法。

    拓展

    saySomething方法里增加属性self.js_name 的打印,其他不变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @interface JSPerson : NSObject

    @property (nonatomic, strong) NSString *js_name;

    - (void)saySomething;

    @end
    @implementation JSPerson
    - (void)saySomething{

    NSLog(@"%s - %@",__func__,self.js_name);
    }
    @end

    再次运行程序发现打印结果为:

    1
    2
    -[JSPerson saySomething] - (null)
    -[JSPerson saySomething] - <JSPerson: 0x600003a00380>
    • [person saySomething]:因为我们没有对js_name赋值,[person saySomething]打印(null)正常。

    • [(__bridge id)js saySomething]:这里打印了<JSPerson: 0x600003a00380>很困惑。

      我们首先要清楚self.js_name是怎么找到js_name并打印的,它是从person内存地址中平移8位(isa是8位)找到第一个属性js_name

      类比js也需要平移8位找js_name,由于js是一个指针,存在上的,栈是一个先进后出的数据结构,每次参数传入就会压栈。

      • 其中隐藏参数会压入栈,且每个函数都会有两个隐藏参数(id self,sel _cmd),这个我们前面探索过,可以通过clangoc代码转成c++代码查看。

      • 隐藏参数压栈的过程,其地址是递减的,而栈是从高地址->低地址 分配的,即在栈中,参数会从前往后一直压

      • 前面还有一行[super viewDidLoad];,super调用的压栈我们也需要研究一下,其实上一题我们研究过它实际调用的是objc_msgSendSuper2,有两个参数_objc_supersel。结构体的属性的压栈我们通过自定义一个结构体探索

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        struct js_struct{
        NSNumber *num1;
        NSNumber *num2;
        } js_struct;
        - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        JSPerson *person1 = [JSPerson alloc];
        struct js_struct jst = {@(1),@(3)};
        JSPerson *person = [JSPerson alloc];
        [person saySomething];

        Class cls = [JSPerson class];
        void  *js = &cls;
        [(__bridge id)js saySomething];
        }

        我们在图示位置添加断点调试

        1627138804130

        使用lldb调试

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        (lldb) p &person1
        (JSPerson **) $0 = 0x00007ffeed1d8118
        (lldb) p &jst
        (js_struct *) $1 = 0x00007ffeed1d8108
        (lldb) p &person
        (JSPerson **) $2 = 0x00007ffeed1d8100
        (lldb) p jst.num1
        (__NSCFNumber *) $3 = 0xbab63c269bab4904 (int)1
        (lldb) p &$3
        (NSNumber **) $4 = 0x00007ffeed1d8108
        (lldb) p jst.num2
        (__NSCFNumber *) $5 = 0xbab63c269bab4924 (int)3
        (lldb) p &$5
        (NSNumber **) $6 = 0x00007ffeed1d8110

        发现num1的地址<num2的地址,说明num2先入栈。也就是结构体是从后向前入栈的

      • 总结来说题中压栈的顺序是self->_cmd->superClass->self->person->cls->js。地址空间是由高到低。所以这个地方js向高地址平移8字节找到的是person也就是打印是<JSPerson: 0x600003a00380>的原因。

Flutter程序的入口main()方法会调用runApp()方法,我们本篇探索runApp都做了啥。

概览

1
2
3
4
5
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}

这个方法一看很简洁,一共调用了WidgetsFlutterBinding的三个方法,在看三个方法的实现之前,我们看一下WidgetsFlutterBinding是什么。

WidgetsFlutterBinding

我们看一下官方的解释:

A concrete binding for applications based on the Widgets framework.This is the glue that binds the framework to the Flutter engine.

翻译过来就是,一个基于Widgets framework的应用程序的具体绑定,它是绑定frameworkFlutter engine的胶水层。

1
2
3
4
5
6
7
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}

它的父类BindingBase是一个抽象类,with实现了很多mixin,这些mixin只能用于继承自BindingBase的类。mixin的作用是扩展功能,mixin可以类比于iOSprotocol(个人见解,如果不对欢迎指正)。

BindingBase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
abstract class BindingBase {
/// 省略代码
/// However, multiple window support is not yet implemented, so currently this
/// provides access to the one and only window.
// TODO(gspencergoog): remove the preceding note once multi-window support is
// active.
// 唯一的window
ui.SingletonFlutterWindow get window => ui.window;
/// 每一个BindingBase类定义行为 都有一个 platformDispatcher 作为回调(handlers)
ui.PlatformDispatcher get platformDispatcher => ui.PlatformDispatcher.instance;
/// 初始化实例
void initInstances() {
assert(!_debugInitialized);
assert(() {
_debugInitialized = true;
return true;
}());
}
/// The current [WidgetsBinding], if one has been created.
/// ensureInitialized方法返回的实例
/// If you need the binding to be constructed before calling [runApp],
/// you can ensure a Widget binding has been constructed by calling the
/// `WidgetsFlutterBinding.ensureInitialized()` function.
static WidgetsBinding? get instance => _instance;
static WidgetsBinding? _instance;
/// 注册 service extensions 初始化之后调用
void initServiceExtensions() {
///省略代码
}
}
  • ui.window:是Flutter App显示的窗口,它继承自FlutterView,位于Flutter engine层。
  • ui.PlatformDispatcher.instance:platformDispatcher是Flutter 的一个事件分发器,负责Flutter分发engine的事件,和传递事件给engine层。
  • initInstances:初始化实例的方法。
  • initServiceExtensions():注册 service extensions,比如platformOverrideactiveDevToolsServerAddress等。

ensureInitialized()方法

该方法的作用是返回一个WidgetsBinding类型实例,如果未创建就新创建一个。

1
2
3
4
5
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}

就是返回一个WidgetsBinding.instance实例,因为WidgetsFlutterBinding实现了GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBindingmixin,mixin的initInstancesinitServiceExtensions方法也会调用,每个mixin的功能:

  • GestureBinding:处理手势。
  • SchedulerBinding: 处理系统调度。
  • ServicesBinding:处理与原生的交互。
  • PaintingBinding:处理绘制。
  • SemanticsBinding:处理语义化。
  • RendererBinding:处理渲染。
  • WidgetsBindingWidgets相关。

我们下面主要看WidgetsBindingRendererBinding

WidgetsBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
// Initialization of [_buildOwner] has to be done after
// [super.initInstances] is called, as it requires [ServicesBinding] to
// properly setup the [defaultBinaryMessenger] instance.
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
///省略代码
}

WidgetsBinding初始化会创建一个BuildOwner对象,它的作用是管理Widget树和Element树。

RendererBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
if (kIsWeb) {
addPostFrameCallback(_handleWebFirstFrame);
}
}

/// The current [RendererBinding], if one has been created.
static RendererBinding? get instance => _instance;
static RendererBinding? _instance;
///省略代码
}

RendererBinding初始化会创建一个PipelineOwner对象,用于管理RenderObject树。PipelineOwnerBuildOwner都位于framework层,它们通过Bingding(胶水层)与engine交互。

  • 初始化了一个PipelineOwner用于管理RenderObject.
  • _handlePersistentFrameCallback这个callback传入SchedulerBinding中的_postFrameCallbacks中,这样在硬件每次发出VSync信号的时候都会调用RenderBinding中的_handlePersistentFrameCallback方法._handlePersistentFrameCallback方法中直接调用了drawFrame方法。

scheduleAttachRootWidget

实例化之后会调用scheduleAttachRootWidget方法。

1
2
3
4
5
6
@protected
void scheduleAttachRootWidget(Widget rootWidget) {
Timer.run(() {
attachRootWidget(rootWidget);
});
}

调用了attachRootWidget方法:

1
2
3
4
5
6
7
8
9
10
11
12
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance!.ensureVisualUpdate();
}
}

attachRootWidget方法用于是为根Widget生成一个根Element。生成Element调用了attachToRenderTree方法并传入了BuildOwner和Element。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
///新创建一个 element
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
// 建立能更新widget 树的能力,可以回调 callback,构建所有标记为dirty的elment
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}

这个方法主要是element为空的时候新建一个element,新建后会调用BuildOwnerbuildScope主要作用是建立能更新widget 树的能力,可以回调 callback,构建所有标记为dirtyelement

attachRootWidget方法,最后会执行SchedulerBinding.instance!.ensureVisualUpdate()

1
2
3
4
5
6
7
8
9
10
11
12
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}

它主要是调用新的帧的调度管理。它会调用scheduleFrame方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
assert(() {
if (debugPrintScheduleFrameStacks)
debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
return true;
}());
///给window设置回调
ensureFrameCallbacksRegistered();
///调度更新
window.scheduleFrame();
_hasScheduledFrame = true;
}

这里会给window设置onBeginFrameonDrawFrame的回调,window会把回调传给platformDispatcher

1
2
3
4
5
6
7
8
9
10
@override
set onBeginFrame(ui.FrameCallback? callback) {
platformDispatcher.onBeginFrame = callback;
}
@override
ui.VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
@override
set onDrawFrame(ui.VoidCallback? callback) {
platformDispatcher.onDrawFrame = callback;
}

也就是说scheduleAttachRootWidget经过一系列调用之后,会把SchedulerBinding_handleBeginFrame_handleDrawFrame传给platformDispatcherplatformDispatcher分发来自enginee的事件。而在这里SingletonFlutterWindowplatformDispatcheronBeginFrameonDrawFrame这两个事件交给SchedulerBinding处理。

当硬件发出VSync信号时,会调用platformDispatcher的onDrawFrame。实际上会调用SchedulerBinding中的_handleDrawFrame方法。_handleDrawFrame会调用handleDrawFrame方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
Timeline.finishSync(); // end the "Animate" phase
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
///省略代码
}
}

_postFrameCallbacks里面存储的是callback,作用是硬件每次发出VSync信号的时候都会调用。这里的_postFrameCallbacks是在RenderBinding这个mixininitInstances方法中传入的

1
addPersistentFrameCallback(_handlePersistentFrameCallback);

scheduleWarmUpFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// We use timers here to ensure that microtasks flush in between.
Timer.run(() {
assert(_warmUpFrame);
handleBeginFrame(null);
});
Timer.run(() {
assert(_warmUpFrame);
handleDrawFrame();
// We call resetEpoch after this frame so that, in the hot reload case,
// the very next frame pretends to have occurred immediately after this
// warm-up frame. The warm-up frame's timestamp will typically be far in
// the past (the time of the last real frame), so if we didn't reset the
// epoch we would see a sudden jump from the old time in the warm-up frame
// to the new time in the "real" frame. The biggest problem with this is
// that implicit animations end up being triggered at the old time and
// then skipping every frame and finishing in the new time.
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// Lock events so touch events etc don't insert themselves until the
// scheduled frame has finished.
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}

这个方法主要调用是scheduleFrame,跟进代码实际是调用的window.scheduleFrame(),

1
2
3
4
@override
void scheduleFrame() {
platformDispatcher.scheduleFrame();
}

window.scheduleFrame()调用了platformDispatcher.scheduleFrame(),通知engine层需要绘制。engine会根据情况尽快地调用platformDispatcher的onDrawFrame方法。

总结

runApp方法主要做了以下事情:

  • 创建WidgetsFlutterBinding它是连接frameworkengine的胶水层。注册Vsync回调,后面每一帧的调用会出发WidgetsFlutterBinding的回调,最后传递到framework层处理逻辑。
  • attachRootWidget:遍历挂载整个视图树,建立widgetelementrenderObjcect的连接关系。
  • scheduleWarmUpFrame:调度帧预热(warmUp)。执行帧绘制handleBeginFramehandleDrawFrame方法。

前面几篇我们探索了的加载过程,本篇我们研究类相关的两个点:类的扩展关联对象

类扩展

clang编译

我们首先在main.m文件中新建一个类JSAnimal,并给类定义扩展,注意扩展要在声明之后实现之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@interface JSAnimal : NSObject

@property (nonatomic,copy)NSString *name;

- (void)sayWow;

@end

@interface JSAnimal ()

@property (nonatomic,copy)NSString *type;

- (void)ex_sayWow;

@end

@implementation JSAnimal

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

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

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

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
JSAnimal *animal = [[JSAnimal alloc] init];
[animal ex_sayWow];
NSLog(@"done");
}
return 0;
}

我们使用clang命令,将main.m转成main.cpp文件,看一下分类的实现

1
clang -rewrite-objc main.m -o main.cpp

main.cpp文件搜索JSAnimal

1626779765218

1626779854673

发现扩展里声明的属性方法编译后和中的在一起,作为的一部分,也就是说扩展中的属性和方法编译期就添加到本类中了。

通过源码探索运行时

定义一个JSPerson类和扩展,本类中实现扩展中声明的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

@interface JSPerson : NSObject

@property (nonatomic, copy) NSString *name;

- (void)saySomething;

@end

#import "JSPerson.h"

@implementation JSPerson

+ (void)load{

}

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

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

+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
1
2
3
4
5
6
7
@interface JSPerson ()

- (void)ext_instanceMethod;

+ (void)ext_classMethod;

@end

注意JSPerson类实现了load方法,目的是让其非懒加载。根据我们的经验,我们在realizeClassWithoutSwift添加断点调试

1626781568401

通过lldb打印ro中的方法列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
(lldb) p ro
(const class_ro_t *) $0 = 0x0000000100004790
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 16
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003b58 "JSPerson"
}
}
baseMethodList = 0x00000001000047d8
baseProtocols = nil
ivars = 0x0000000100004840
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100004868
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethods()
(method_list_t *) $2 = 0x00000001000047d8
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 4)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
name = "saySomething"
types = 0x0000000100003d84 "v16@0:8"
imp = 0x0000000100003600 (KCObjcBuild`-[JSPerson saySomething])
}
(lldb) p $3.get(1).big()
(method_t::big) $5 = {
name = "ext_instanceMethod"
types = 0x0000000100003d84 "v16@0:8"
imp = 0x0000000100003630 (KCObjcBuild`-[JSPerson ext_instanceMethod])
}
(lldb) p $3.get(2).big()
(method_t::big) $6 = {
name = "name"
types = 0x0000000100003d98 "@16@0:8"
imp = 0x0000000100003660 (KCObjcBuild`-[JSPerson name])
}
(lldb) p $3.get(3).big()
(method_t::big) $7 = {
name = "setName:"
types = 0x0000000100003da0 "v24@0:8@16"
imp = 0x0000000100003690 (KCObjcBuild`-[JSPerson setName:])
}

可以看到,扩展中的方向现在已经加载了已知ro中的方法是编译期就确定的,所以也验证了扩展的方法是在编译期添加到本类的。

小结

  • 类的扩展 在编译期 会作为类的一部分,和类一起编译进来
  • 类的扩展只是声明,依赖于本类的实现。

分类的关联对象

我们知道分类正常是不能添加属性的,但是通过关联对象可以,其实现通过两个方法

  • 通过objc_setAssociatedObject方法设置值。
  • 通过objc_getAssociatedObject方法取值。

下面我们分别探索。

objc_setAssociatedObject流程

objc_setAssociatedObject有四个参数:

  • 参数1:要关联的对象
  • 参数2:表示符,方便查找识别
  • 参数3:value值
  • 参数4:属性的策略,我们定义属性经常用到的如nonatomicstrongweak

首先定义JSPerson分类,定义一个属性cateegory_name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface JSPerson (JSCategory)

@property (nonatomic, copy) NSString *cateegory_name;

@end
@implementation JSPerson (JSCategory)

- (void)setCateegory_name:(NSString *)category_name{
objc_setAssociatedObject(self, "category_name", category_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)category_name{
return objc_getAssociatedObject(self, "category_name");
}

@end

main函数赋值属性的地方添加断点,根据调用情况

1626783154111

定位到objc_setAssociatedObject方法

1626783226697

调用的是_object_set_associative_reference方法,我们继续跟进查看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;

if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
///将object封装一下 类型为DisguisedPtr
DisguisedPtr<objc_object> disguised{(objc_object *)object};
///包装policy value
ObjcAssociation association{policy, value};

// retain the new value (if any) outside the lock.
//根据策略类型(strong、weak等)进行处理
association.acquireValue();

bool isFirstAssociation = false;
{
//初始化manager变量,相当于自动调用AssociationsManager的构造函数进行初始化
AssociationsManager manager;
///一个HashMap
AssociationsHashMap &associations(manager.get());

if (value) {
//返回的结果是一个类对
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}

/* establish or replace the association 建立或者替换关联*/
auto &refs = refs_result.first->second;///得到一个空的桶,找到引用对象类型,即第一个元素的second值
auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有association关联对象
if (!result.second) {///如果结果不存在
association.swap(result.first->second);
}
} else {//如果传的是空值,则移除关联,相当于移除
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);

}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).释放老的关联值
association.releaseHeldValue();
}

通过源码我们看到大体的流程为:

  • 创建一个AssociationsManager管理类
  • 获取静态哈希表:associations
  • 判断关联值value是否为空
    • 如果为空就走:插入空值流程。
    • 如果不为空继续下一步
  • 通过try_emplace方法,创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 如果发现没有 key插入一个 空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象
  • 用当前 policy 和 value 组成了一个 ObjcAssociation 替换原来 BucketT中的值
  • 标记一下 ObjectAssociationMap第一次false

源码调试

对流程有了大概的认识,我们开始断点调试

if (value) 之前变量的值

1626785173962

通过lldb我们打印出了disguisedassociationmanagerassociationsvalue的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069393517536)
(lldb) p association
(objc::ObjcAssociation) $1 = {
_policy = 3
_value = 0x0000000100004080 "哈哈哈"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
Buckets = nil
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
(lldb) p value
(__NSCFConstantString *) $4 = 0x0000000100004080 "哈哈哈"
value不为空流程

上面我们看到value值不为空,我们进入if语句继续调试。

  • p refs_result
1
2
3
4
5
6
7
8
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $5 = {
first = {
Ptr = 0x00000001012102a0
End = 0x0000000101210300
}
second = true
}

看到refs_result的数据结构看起来比较复杂,但是值比较简单,有两个属性firstsecond。其中first的值为:

1
2
3
4
(lldb) p $5.first.Ptr
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $6 = 0x00000001012102a0
(lldb) p $5.first.End
(objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>::pointer) $7 = 0x0000000101210300

second值为true,所以会执行isFirstAssociation = true

  • try_emplace方法,associations调用了try_emplace方法,我们看一下他的源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    template <typename... Ts>
    std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    ///根据key找桶
    if (LookupBucketFor(Key, TheBucket))
    ///如果桶存在 则返回
    return std::make_pair(
    makeIterator(TheBucket, getBucketsEnd(), true),
    false); // Already in map.

    // Otherwise, insert the new element.
    ///如果不存在则插入桶 并返回
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
    makeIterator(TheBucket, getBucketsEnd(), true),
    true);
    }

    有两个返回,都是通过std::make_pair生成相应的键值对。

    1. 通过LookupBucketFor方法查找桶,如果map中已经存在,则直接返回,其中make_pair的第二个参数bool值为false
    2. 如果没有找到,则通过InsertIntoBucket插入map,其中make_pair的第二个参数bool值为true

    我们断点进来使用lldb调试

    1626787367239

    p TheBucket

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (lldb) p TheBucket
    (objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $1 = 0x0000000101c04200
    (lldb) p *$1
    (objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $2 = {
    std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
    first = (value = 18446744069384153152)
    second = {
    Buckets = nil
    NumEntries = 0
    NumTombstones = 0
    NumBuckets = 0
    }
    }
    }

    看到TheBucket的类型与 refs_result中属性的类型是一致的。

  • LookupBucketFor方法

    我们进入LookupBucketFor源码发现有两个实现,它们的区别是FoundBucket的参数类型第一个实现多const修饰。

    1626786329854

    我们通过断点调试,发现调用的是第2个实现,第2个方法内部调用了第1个实现。我们先看第1个实现源码,注释中有流程说明。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    template<typename LookupKeyT>
    bool LookupBucketFor(const LookupKeyT &Val,
    const BucketT *&FoundBucket) const {
    const BucketT *BucketsPtr = getBuckets();
    const unsigned NumBuckets = getNumBuckets();

    if (NumBuckets == 0) {
    FoundBucket = nullptr;
    return false;
    }
    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    const KeyT EmptyKey = getEmptyKey();
    const KeyT TombstoneKey = getTombstoneKey();
    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
    !KeyInfoT::isEqual(Val, TombstoneKey) &&
    "Empty/Tombstone value shouldn't be inserted into map!");
    ///通过哈希函数得到BucketNo
    unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
    unsigned ProbeAmt = 1;
    while (true) {//与catche_t查找imp类似,通过哈希查找
    const BucketT *ThisBucket = BucketsPtr + BucketNo;
    // Found Val's bucket? If so, return it. 如果找到直接返回 true
    if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
    FoundBucket = ThisBucket;
    return true;
    }

    // If we found an empty bucket, the key doesn't exist in the set.
    // Insert it and return the default value.
    //如果是一个空桶 说明key不在集合中,将key插入 返回false
    if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
    // If we've already seen a tombstone while probing, fill it in instead
    // of the empty bucket we eventually probed to.
    FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
    return false;
    }

    // If this is a tombstone, remember it. If Val ends up not in the map, we
    // prefer to return it than something that would require more probing.
    // Ditto for zero values.
    // 以上条件都不满足 BucketNo调整进行平移、再哈希继续查找
    if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
    !FoundTombstone)
    FoundTombstone = ThisBucket; // Remember the first tombstone found.
    if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
    FoundTombstone = ThisBucket;

    // Otherwise, it's a hash collision or a tombstone, continue quadratic
    // probing.
    if (ProbeAmt > NumBuckets) {
    FatalCorruptHashTables(BucketsPtr, NumBuckets);
    }
    BucketNo += ProbeAmt++;
    BucketNo &= (NumBuckets-1);
    }
    }

    第2个LookupBucketFor的实现

    1
    2
    3
    4
    5
    6
    7
    bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
    const BucketT *ConstFoundBucket;//空的桶
    bool Result = const_cast<const DenseMapBase *>(this)
    ->LookupBucketFor(Val, ConstFoundBucket);//调用第一个LookupBucketFor方法查找
    FoundBucket = const_cast<BucketT *>(ConstFoundBucket);//如果找到复制给第二个参数,因为第二个参数是引用类型会直接让调用的地方获取到值。也就是try_emplace方法的TheBucket
    return Result;
    }
  • 继续走valuetrue的流程

    后面还会执行try_emplace方法,我们在执行之前查看一下refs的值

    1
    2
    3
    4
    5
    6
    7
    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $3 = {
    Buckets = nil
    NumEntries = 0
    NumTombstones = 0
    NumBuckets = 0
    }

    try_emplace方法之后refs的值

    1
    2
    3
    4
    5
    6
    7
    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $4 = {
    Buckets = 0x0000000100711390
    NumEntries = 1
    NumTombstones = 0
    NumBuckets = 4
    }

    第一次执行try_emplace插入的是一个空桶,还没有值,第二次执行第一次执行try_emplace才插入值,即往空桶中插入ObjectAssociationMap(value,policy),返回true。

    此时result.secondtrue,此时属性的value就关联上了。

关联对象结构

关联对象的设置图如下:

关联对象

属性设计的哈希表结构如下:

关联对象哈希表

map中有很多的关联对象map,类型是ObjectAssociationMap,其中key为DisguisedPtr<objc_object>,例如JSPerson会对应一个ObjectAssociationMapJSTeacher也会对应一个ObjectAssociationMap

ObjectAssociationMap哈希表中有很多key-value键值对,其中key的类型为const void *,其实这个key从底层这个方法_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)的参数就可以看出,key是我们关联属性时设置的字符串value的类型为ObjcAssociation

value为空流程

这个过程其实就是else流程,也就是我们对value设置为nil的流程,主要就是移除关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);

}
}
}
}
  • 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 清理迭代器
  • 实际上如果插入空置 相当于清除

objc_getAssociatedObject流程

main方法中添加一个取值的语句
1
2
3
4
5
6
7
8
9
int main(int argc, const char * argv[]) {
@autoreleasepool {
JSPerson *person = [[JSPerson alloc] init];
person.category_name = @"哈哈哈";
NSString *name = person.category_name;
NSLog(@"done");
}
return 0;
}
objc_getAssociatedObject源码实现
1
2
3
4
5
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}

调用_object_get_associative_reference函数。

_object_get_associative_reference源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};

{
///创建AssociationsManager管理类
AssociationsManager manager;
///获取静态哈希表
AssociationsHashMap &associations(manager.get());
/////找到迭代器,即获取buckets
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {//如果这个迭代查询器不是最后一个 继续获取
//找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
ObjectAssociationMap &refs = i->second;
//根据key查找ObjectAssociationMap,即获取bucket
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
//获取ObjcAssociation
association = j->second;
association.retainReturnedValue();
}
}
}
///返回值
return association.autoreleaseReturnedValue();
}

看源码分析主要分为以下几步

  • 创建一个 AssociationsManager 管理类
  • 获取静态哈希表:AssociationsHashMap
  • 通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 如果这个迭代查询器不是最后一个 继续获取 : ObjectAssociationMap (policy和value)
  • 通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回 value
查找方法find
1
2
3
4
5
6
iterator find(const_arg_type_t<KeyT> Val) {
BucketT *TheBucket;
if (LookupBucketFor(Val, TheBucket))
return makeIterator(TheBucket, getBucketsEnd(), true);
return end();
}

根据关联对象迭代查找AssociationsHashMap,也就是buckets

通过源码看取值流程

我们直接断点到_object_get_associative_reference函数

1626789232319

执行p ip i->second:

1
2
3
4
5
6
7
8
9
10
11
12
(lldb) p i
(objc::DenseMapBase<objc::DenseMap<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >, DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > >::iterator) $0 = {
Ptr = 0x0000000100631d60
End = 0x0000000100631d80
}
(lldb) p i->second
(objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $1 = {
Buckets = 0x0000000100631d80
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}

再次执行find方法,在调用find方法之前,我们先打印j,此时valuenil

1
2
3
4
5
6
7
8
9
10
11
(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $2 = {
Ptr = 0x00007ffeefbff400
End = 0x00000001002e70db
}
(lldb) p j->second
(objc::ObjcAssociation) $3 = {
_policy = 4294980472
_value = nil
}
(lldb)

执行find方法之后再次打印,发现value已经有值,也就是取到了关联对象。

1
2
3
4
5
6
7
8
9
10
(lldb) p j
(objc::DenseMapBase<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >::iterator) $4 = {
Ptr = 0x0000000100631d80
End = 0x0000000100631de0
}
(lldb) p j->second
(objc::ObjcAssociation) $5 = {
_policy = 3
_value = 0x0000000100004080 "哈哈哈"
}

总结

本篇主要探索了扩展关联对象,其中类的扩展编译期 会作为类的一部分,和类一起编译进来。

关联对象设置流程为:

  • 创建一个AssociationsManager管理类
  • 获取静态哈希表:associations
  • 判断关联值value是否为空
    • 如果为空就走:插入空值流程。
    • 如果不为空继续下一步
  • 通过try_emplace方法,创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 如果发现没有 key插入一个 空的 BucketT进去并返回true
  • 通过setHasAssociatedObjects方法标记对象存在关联对象
  • 用当前 policy 和 value 组成了一个 ObjcAssociation 替换原来 BucketT中的值
  • 标记一下 ObjectAssociationMap第一次false

关联对象取值的流程为:

  • 创建一个 AssociationsManager 管理类
  • 获取静态哈希表:AssociationsHashMap
  • 通过find方法根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 如果这个迭代查询器不是最后一个 继续获取 : ObjectAssociationMap (policy和value)
  • 通过find方法找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 返回 value

本文我们研究分类的加载流程。

分类的本质

在研究对象的本质的时候,我们都用clang命令将main.m转换成main.cpp文件查看其本质,分类也不例外,我们使用相同的方法分析。

首先我们定义一个JSPerson的分类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface JSPerson (TestLoad)

- (void)sayCategory;

@end
@implementation JSPerson (TestLoad)

- (void)sayCategory{
NSLog(@"JSPersonCategpry say : %s",__func__);
}

+ (void)load{
NSLog(@"JSPersonCategpry load");
}
@end

然后在main.m文件中使用:

1
2
3
4
5
6
7
int main(int argc, const char * argv[]) {
@autoreleasepool {
JSPerson *p = [JSPerson alloc];
[p sayCategory];
NSLog(@"Hello, World!");
}
}

我们执行:

1
clang -rewrite-objc main.m -o main.cpp

我们打开main.cpp文件,找到分类相关的代码。

1
2
3
4
5
6
7
8
9
10
11
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_JSPerson_$_TestLoad,
};
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};

我们从源码里也能搜索到它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};

可以看到分类的本质是结构体category_t

我们继续回到_read_images函数探索。

rwe的赋值

1
auto rwe = cls->data()->extAllocIfNeeded();

extAllocIfNeeded方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
runtimeLock.assertLocked();

auto rwe = objc::zalloc<class_rw_ext_t>();

rwe->version = (ro->flags & RO_META) ? 7 : 0;

method_list_t *list = ro->baseMethods();
if (list) {
if (deepCopy) list = list->duplicate();
rwe->methods.attachLists(&list, 1);
}

// See comments in objc_duplicateClass
// property lists and protocol lists historically
// have not been deep-copied
//
// This is probably wrong and ought to be fixed some day
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rwe->properties.attachLists(&proplist, 1);
}

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

set_ro_or_rwe(rwe, ro);
return rwe;
}

auto rwe = cls->data()->extAllocIfNeeded();是进行rwe的创建,那么为什么要在这里进行rwe的初始化??因为我们现在要做一件事:往本类添加属性、方法、协议等,即对原来的 clean memory要进行处理了

  • 进入extAllocIfNeeded方法的源码实现,判断rwe是否存在,如果存在则直接获取,如果不存在则开辟
  • 进入extAlloc源码实现,即对rwe 0-1的过程,在此过程中,就将本类的data数据加载进去了
  • 其中关键代码是rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);即存入mlists的末尾,mlists的数据来源前面的for循环
  • 在调试运行时,发现category_t中的name编译时是JSPerson(参考clang编译时的那么),运行时是TestLoad即分类的名字
  • 代码mlists[ATTACH_BUFSIZ - ++mcount] = mlist;,经过调试发现此时的mcount等于1,即可以理解为 倒序插入,64的原因是允许容纳64个(最多64个分类)

小结

本类 中 需要添加属性、方法等,所以需要初始化rwe,rwe的初始化主要涉及:分类、addMethod、addProperty、addprotocol , 即对原始类进行修改或者处理时(运行时),才会进行rwe的初始化

attachCategories

通过attachCategories反推分类的加载,我们libobjc源码全局搜索attachCategories的调用,发现有两个地方调用:attachToClassload_categories_nolock

####attachToClass

调用attachToClass的方法只有一个methodizeClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void methodizeClass(Class cls, Class previously)
{
///省略代码
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);
///省略代码
}

这里有一个previously执行条件,我们依次网上查找调用链,发现previously==nil,previously参数只是方便动态化调试,所以实际调用的只有下面这一处代码:

1
2
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

load_categories_nolock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();

size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};

if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}

// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}

if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};

processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}

全局搜素load_categories_nolock,发现调用load_categories_nolock的地方有两处

  • loadAllCategories
  • _read_images

但是经过调试发现,是不会走_read_images方法中的if流程的,而是走的loadAllCategories方法中的。

attachLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;

if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;

for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}

这个函数一共三部分我们分别看:

1
2
3
4
5
else if (!list  &&  addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}

addedLists[0]赋值给list,list是一维数组。

1
2
3
4
5
6
7
8
9
10
11
12
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}

这个情况是list不为空,新建一个扩容的数组,将之前的数据放在lists[addedCount]位置,新元素放到之前list的前面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;

for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}

这段其实和上次类似,依然将新数组插入到前面,新数组中的新元素在数组首部。

分类加载的四种情况

根据分类是否实现+load()方法分为四种情况。

我们先定义JSPerson及它的分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@interface JSPerson : NSObject

- (void)sayHello;

@end
#import "JSPerson.h"

@implementation JSPerson

- (void)sayHello{

NSLog(@"JSPerson say : Hello!!!");
}

+ (void)load{}

@end
@interface JSPerson (Test)

- (void)saySomething;

@end
#import "JSPerson+Test.h"

@implementation JSPerson (Test)

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

+ (void)load{}

@end

非懒加载分类和非懒加载类

我们在JSPerson类和分类中都实现load,我们在realizeClassWithoutSwift添加断点

类和分类都是非懒加载分类加载

我们用lldb打印当前ro的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
readClass JSPerson....
_read_images JSPerson....
(lldb) p ro
(const class_ro_t *) $0 = 0x0000000100004280
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 8
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003bbc "JSPerson"
}
}
baseMethodList = 0x00000001000042c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethods()
(method_list_t *) $2 = 0x00000001000042c8
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
name = "sayHello"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000036b0 (KCObjcBuild`-[JSPerson sayHello])
}

发现此时只有的方法,并没有分类的方法,说明分类目前还没有加载。

我们在attachCategories添加断点,继续执行代码:

非懒加载分类

继续用lldb调试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
(lldb) p mlist
(method_list_t *) $5 = 0x0000000100004420
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
}
(lldb) p $6.get(0).big()
(method_t::big) $7 = {
name = "saySomething"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000037f0 (KCObjcBuild`-[JSPerson(Test) saySomething])
}
(lldb)

说明现在分类被加载了,被加载到了rwe中:

1
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);

注意:ATTACH_BUFSIZ=64也就是说分类的方法个数不能大于64。

非懒加载分类和懒加载类

我们删除JSPerson类的load方法,重新运行程序。

类和分类都是非懒加载分类加载

继续在我们第一个断点位置使用lldb调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
_read_images JSPerson....
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000042a8
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 8
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003bbc "JSPerson"
}
}
baseMethodList = 0x0000000100004178
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethods()
(method_list_t *) $2 = 0x0000000100004178
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 2)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
name = "saySomething"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000037f0 (KCObjcBuild`-[JSPerson(Test) saySomething])
}
(lldb) p $3.get(1).big()
(method_t::big) $5 = {
name = "sayHello"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000036b0 (KCObjcBuild`-[JSPerson sayHello])
}
(lldb)

发现分类已经加载了,也加载了,说明非懒加载类会使懒加载的类在启动时提前加载(如果没有分类是第一次调用时加载),说明加载的时机是编译期

懒加载分类和非懒加载类

我们删除JSPerson分类的load方法,重新运行程序。

类和分类都是非懒加载分类加载

使用lldb调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000042a8
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 8
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003bbc "JSPerson"
}
}
baseMethodList = 0x0000000100004158
baseProtocols = 0x0000000000000000
ivars = 0x0000000000000000
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000000000000
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethods()
(method_list_t *) $2 = 0x0000000100004158
(lldb) p *$2
(method_list_t) $3 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 2)
}
(lldb) p $3.get(0).big()
(method_t::big) $4 = {
name = "saySomething"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000037f0 (KCObjcBuild`-[JSPerson(Test) saySomething])
}
(lldb) p $3.get(1).big()
(method_t::big) $5 = {
name = "sayHello"
types = 0x0000000100003cfd "v16@0:8"
imp = 0x00000001000036c0 (KCObjcBuild`-[JSPerson sayHello])
}
(lldb)

发现分类都已经加载了,说明加载的时机也是编译期

懒加载分类和懒加载类

我们把分类load方法都删除,重新运行程序

懒加载分类第一次调用

依然走到断点,注意观察左边的调用栈,发现是从lookUpImpOrForward,说明是在第一次调用方法的时候加载的。

One More Condition

前面四种情况基本能包括了分类,但是还有种情况就是:有多个分类,部分分类实现了load方法主类也实现了load。我们就探索一下这个情况,新建一个JSPerson的分类

1
2
3
4
5
6
7
8
9
10
11
12
@interface JSPerson (Test2)

- (void)saySomething2;

@end
@implementation JSPerson (Test2)

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

@end

根据前面我们其实应清楚,就是实现load的分类肯定会在运行时加载,我们关注的点就在于没有实现load方法的分类是什么时候加载的呢也就是attachCategories是否会加载未实现load方法的分类,运行程序

多个分类加载

使用lldb调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
lldb) p cat
(category_t *) $1 = 0x0000000100004448
(lldb) p *$1
(category_t) $2 = {
name = 0x0000000100003b8f "Test2"
cls = 0x00000001000049a0
instanceMethods = {
ptr = 0x0000000100004428
}
classMethods = {
ptr = 0x0000000000000000
}
protocols = 0x0000000000000000
instanceProperties = 0x0000000000000000
_classProperties = 0x0000000000000000
}
operator() -JSPerson....
attachCategories -JSPerson....
(lldb) p cat
(category_t *) $3 = 0x00000001000044c8
(lldb) p *$3
(category_t) $4 = {
name = 0x0000000100003b95 "Test"
cls = 0x00000001000049a0
instanceMethods = {
ptr = 0x0000000100004488
}
classMethods = {
ptr = 0x00000001000044a8
}
protocols = 0x0000000000000000
instanceProperties = 0x0000000000000000
_classProperties = 0x0000000000000000
}

调试我们发现,两个分类都加载了,也就是只要有一个分类实现了load,其他分类都会在启动时加载。

总结

本篇主要是探索了分类的加载,主要分为5种情况

  • 非懒加载类和非懒加载分类:此时分类是在运行时,也就是程序启动的时候加载的。
  • 懒加载类和非懒加载分类:此时分类是在编译时加载
  • 非懒加载类和懒加载分类:此时分类也是在编译时加载
  • 懒加载类和懒加载分类:此时分类在第一次调用时加载。
  • 非懒加载类,多个分类,部分是非懒加载分类:此时所有分类都是在程序启动时加载。

上一篇我们探索到了readClass函数,只是对函数名字进行了赋值,并没有对rorw进行操作,本篇我们就继续探索_read_images函数剩下的调用。

realizeClass的引入

因为我们探索的目的是的加载,我们先忽略protocolcategories的地方。为了调试代码,我们首先还是先创建一个JSPerson类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@interface JSPerson : NSObject
@property (nonatomic, strong) NSString *nickName;

- (void)say1;
- (void)say2;

+ (void)sayHappy;
@end

#import "JSPerson.h"
@implementation JSPerson

- (void)say1{
NSLog(@"JSPerson say : %s",__func__);
}
- (void)say2{
NSLog(@"JSPerson say : %s",__func__);
}

+ (void)load{
NSLog(@"load");
}
+ (void)sayHappy{
NSLog(@"JSPerson say : %s",__func__);
}
@end

接下来我们继续看_read_images函数,发现和类相关的地方有两个地方,realize non-lazy classesrealize future classes,我们在两段代码中加入我们调试的代码,为了观察我们自定义类的加载情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
///调试代码 确定是我们自定义的类
const char *mangledName = cls->nonlazyMangledName();
const char *customerClassName = "JSPerson";
if (strcmp(mangledName, customerClassName) == 0) {
//打印类名
printf("%s -: non-lazy classes要研究的类: - %s\n",__func__,mangledName);
}

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");
// Realize newly-resolved future classes, in case CF manipulates them
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");
}
///调试代码 确定是我们自定义的类
const char *mangledName = cls->nonlazyMangledName();
const char *customerClassName = "JSPerson";
if (strcmp(mangledName, customerClassName) == 0) {
//打印类名
printf("%s -: realize future classes要研究的类: - %s\n",__func__,mangledName);
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");

在两行printf的地方打断点,运行源码程序,看是否执行到断点位置。发现打印了_read_images -: non-lazy classes要研究的类: - JSPerson,代码执行到了non-lazy classes,里面类加载的核心代码在realizeClassWithoutSwift函数,我们继续探索realizeClassWithoutSwift函数。

realizeClassWithoutSwift分析

操作之前的ro

我们从上到下依次阅读代码,定位到auto ro = (const class_ro_t )cls->data();,因为roclean Memory里的数据我们比较敏感,我们在图示位置打断点:

ro赋值之前

使用LLDB调试程序,查看ro里的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000080c0
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 16
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "JSPerson" {
Value = 0x0000000100003db0 "JSPerson"
}
}
baseMethodList = 0x0000000100008108
baseProtocols = 0x0000000000000000
ivars = 0x0000000100008170
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008198
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1.baseMethodList
(void *const) $2 = 0x0000000100008108
(lldb) p *$2
(lldb)

通过打印我们发现此时ro里的baseMethodList为空,目前还不清楚什么时候赋值的,我们继续探索。

rw的赋值

接下来就是对rw的赋值,注意rwdirty Memory

1
2
3
4
5
6
7
8
9
10
11
12
13
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 {
// 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);
}

isa和superClass赋值

后面的代码就是对isasuperClass的赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#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.
///元类 不是non pointer ISA
cls->setInstancesRequireRawIsa();
} 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 也会是纯的isa
// 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) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);

realizeClassWithoutSwift函数最后会调用methodizeClass,我们下一小节探索methodizeClass

methodizeClass分析

methodizeClass顾名思义就是对方法的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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)" : "");
}
///调试代码 确定是我们自定义的类
const char *mangledName = cls->nonlazyMangledName();
const char *customerClassName = "JSPerson";
if (strcmp(mangledName, customerClassName) == 0) {
//打印类名
if (!isMeta) {
printf("%s -: non-lazy classes要研究的类: - %s\n",__func__,mangledName);
}
}
// Install methods and properties that the class implements itself.
//取出方法列表
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
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
}

断点进入图示位置,此时方法列表还是不能打印

methodList打印

prepareMethodLists

我们继续探索,后面执行prepareMethodLists函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
runtimeLock.assertLocked();
if (addedCount == 0) return;
// There exist RR/AWZ/Core special cases for some class's base methods.
// But this code should never need to scan base methods for RR/AWZ/Core:
// default RR/AWZ/Core cannot be set before setInitialized().
// Therefore we need not handle any special cases here.
if (baseMethods) {
ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
} else if (cls->cache.isConstantOptimizedCache()) {
cls->setDisallowPreoptCachesRecursively(why);
} else if (cls->allowsPreoptInlinedSels()) {
#if CONFIG_USE_PREOPT_CACHES
SEL *sels = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_START];
SEL *sels_end = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_END];
if (method_lists_contains_any(addedLists, addedLists + addedCount, sels, sels_end - sels)) {
cls->setDisallowPreoptInlinedSelsRecursively(why);
}
#endif
}
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.

for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[i];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
//核心代码
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}

// If the class is initialized, then scan for method implementations
// tracked by the class's flags. If it's not initialized yet,
// then objc_class::setInitialized() will take care of it.
if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
}
}

核心调用是fixupMethodList函数。

fixupMethodList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);

// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}

核心的代码是stable_sort,我们分别打印排序前后方法列表,如图示

打印前后排序

注意:这里一定要先在realizeClassWithoutSwift判断好是我们要研究的JSPerson类,然后再看打印结果,否则系统类也会有很多打印,影响我们分析。

1
2
3
4
5
6
7
8
9
methodizeClass -: non-lazy classes要研究的类: - JSPerson
****************sort之前 : say1 - 0x100003dda
sort之前 : say2 - 0x100003ddf
sort之前 : nickName - 0x7fff73fb8a1c
sort之前 : setNickName: - 0x7fff73fb8362
****************sort之后 : say1 - 0x100003dda
sort之后 : say2 - 0x100003ddf
sort之后 : setNickName: - 0x7fff73fb8362
sort之后 : nickName - 0x7fff73fb8a1c

通过上面打印结果:

  • 排序前:say1 - 0x100003ddasay2 - 0x100003ddfnickName - 0x7fff73fb8a1csetNickName: - 0x7fff73fb8362
  • 排序后:say1 - 0x100003ddasay2 - 0x100003ddfsetNickName: - 0x7fff73fb8362nickName - 0x7fff73fb8a1c
  • 排序是根据地址由低到高排序的。

小结

到目前为止,类的加载流程是:_read_images->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList

懒加载类和非懒加载类

我们前面探索的其实都是非懒加载的类懒加载类非懒加载的类的区别就是是否实现了load方法

非懒加载

通过上面的分析,我们已经很清楚了,是在_objc_init方法里加载的,也就是程序启动的时候。这也就是为什么load方法过多,会影响我们应用的启动速度

懒加载类

因为非懒加载类效率低,会影响我们的启动速度,那懒加载类是什么时候加载的呢?我们删掉JSPerson类的load方法,然后在main函数中实例化一个JSPerson实例

1
2
3
4
5
6
7
8
int main(int argc, const char * argv[]) {
@autoreleasepool {
JSPerson *p = [JSPerson alloc];
[p say1];
NSLog(@"Hello, World!");
}
return 0;
}

我们首先在main方法里添加断点,执行程序。走到main函数之后,然后再在realizeClassWithoutSwift添加断点:

懒加载类断点

断点走进来之后我们bt打印调用栈信息:

懒加载类

发现调用是从lookUpImpOrForward开始。

所以我们的结论是懒加载的类是在第一次被使用的时候加载的。

总结

  • 非懒加载类:程序运行时加载,_read_images->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList
  • 懒加载类:第一次使用时加载,lookUpImpOrForward->realizeClassWithoutSwift->methodizeClass->prepareMethodLists->fixupMethodList

我们开发中经常会写分类,它是什么时候加载的及加载的流程,我们下一篇再探索。

上一篇我们主要探索了dyld的链接加载,本篇开始我们探索运行时类的加载过程,本篇只是引子。

_objc_init

首先看一下_objc_init方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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();
//关于线程key的绑定
tls_init();
//运行C ++静态构造函数
static_init();
//runtime运行时环境初始化
runtime_init();
//初始化libobjc的异常处理系统
exception_init();
#if __OBJC2__
//缓存条件初始化
cache_t::init();
#endif
//启动回调机制
_imp_implementationWithBlock_init();
//dyld 注册的地方
//map_images:dyld将image镜像文件加载进内存时,会触发该函数
//load_images:dyld初始化image会触发该函数
//unmap_image:dyld将image移除时会触发该函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}

我们可以看到,_objc_init主要是执行一些初始化方法,包括

  • environ_init():读取影响运行时的环境变量,如果需要可以打印环境变量提供帮助。
  • tls_init():关于线程key的绑定,例如每线程数据的析构函数。
  • static_init():运行C++静态构造函数。
  • runtime_init():runtime运行时环境的初始化,后面我们详细分析。
  • exception_init():初始化libobjc的异常处理系统。
  • cache_t::init():缓存条件初始化。
  • _imp_implementationWithBlock_init():启动回调机制。
  • _dyld_objc_notify_register:dyld的注册。

####environ_init 环境变量初始化

environ_init的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}

可以看出来就是一些环境变量的初始化,参看这段代码,我们可以打印环境变量。

  • 将条件去掉直接调用for循环

    1
    2
    3
    4
    5
    for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
    const option_t *opt = &Settings[i];
    _objc_inform("%s: %s", opt->env, opt->help);
    _objc_inform("%s is set", opt->env);
    }
  • 通过终端命令export OBJC_hrlp = 1,打印环境变量

我们平时项目中可能会有几个环境变量可以在我们的xcode配置(target -- Edit Scheme -- Run --Arguments -- Environment Variables)一下对应的值,达到修改环境变量的目的:

  • DYLD_PRINT_STATISTICS:设置 DYLD_PRINT_STATISTICSYES,控制台就会打印 App 的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,这个我们做启动优化会用到。
  • OBJC_DISABLE_NONPOINTER_ISA:不使用nonpointer isa(nonpointer isa指针地址 末尾为1 ),生成的都是普通的isa,这个我们项目里一般不会改,探索源码的时候可以尝试查看两种isa结构的区别。
  • OBJC_PRINT_LOAD_METHODS:打印 ClassCategory+ (void)load 方法的调用信息,启动优化也可以参考,因为load方法过多也会使启动变慢。

tls_init:线程key的绑定

主要作用是本地线程池的初始化以及析构,源码:

1
2
3
4
5
6
7
8
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++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数 先于 自定义的C++构造函数 运行

1
2
3
4
5
6
7
8
9
10
11
12
13
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}

runtime_init 运行时环境初始化

主要是运行时的初始化,主要分为两部分:分类初始化类的表初始化,我们下一篇会详细探索。

1
2
3
4
5
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}

exception_init 异常初始化

主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理

1
2
3
4
5
6
7
8
9
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
  • _objc_terminate的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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)();
    }
    }
    }

    crash的时候会执行_objc_terminate方法,最后会执行uncaught_handler抛出异常。

  • uncaught_handler:

    1
    2
    3
    4
    5
    6
    7
    8
    objc_uncaught_exception_handler 
    objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
    {
    // fn为设置的异常句柄 传入的函数,为外界给的
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn; //赋值
    return result;
    }

    主要用来处理异常,在我们的App中可以添加一个异常句柄NSSetUncaughtExceptionHandler来处理异常。

cache_init:缓存初始化

主要就是缓存初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}

_imp_implementationWithBlock_init:启动回调机制

该方法主要是启动回调机制,看代码iOS端没做任何处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:dyld注册

这个方法我们上一篇有提到过,主要是

  • 仅供objc运行时使用
  • 注册处理程序,以便在映射、取消映射和初始化objc图像时调用
  • dyld将会通过一个包含objc-image-info的镜像文件的数组回调mapped函数

三个参数的作用:

map_images:dyld将image(镜像文件)加载进内存时,会触发该函数

load_image:dyld初始化image会触发该函数

unmap_image:dyld将image移除时,会触发该函数

我们_objc_init调用的方法有了初步认识,接下来我们开始探索类的加载。

_read_images

_read_images的引入

_dyld_objc_notify_register第一个参数是&map_images,所以我们从map_images开始探索。

1
2
3
4
5
6
7
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比较简单,调用map_images_nolock,我们继续探索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;

// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) {
preopt_init();
}

if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}


// Find all images with Objective-C metadata.
hCount = 0;

// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];

auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}

if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
#if __OBJC2__
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif

#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"Objective-C garbage collection "
"is no longer supported.");
}
#endif
}

hList[hCount++] = hi;

if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}

// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
arr_init();

#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.

for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"%s requires Objective-C garbage collection "
"which is no longer supported.", hi->fname());
}
}
#endif

#if TARGET_OS_OSX
// Disable +initialize fork safety if the app is too old (< 10.13).
// Disable +initialize fork safety if the app has a
// __DATA,__objc_fork_ok section.

// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
// DisableInitializeForkSafety = true;
// if (PrintInitializing) {
// _objc_inform("INITIALIZE: disabling +initialize fork "
// "safety enforcement because the app is "
// "too old.)");
// }
// }

for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app has "
"a __DATA,__objc_fork_ok section");
}
}
break; // assume only one MH_EXECUTE image
}
#endif
}
if (hCount > 0) {
///核心方法
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

firstTime = NO;

// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}

map_images_nolock方法比较长,很大一部分代码是读取镜像的准备,核心调用是_read_images方法。

_read_images分析

_read_images有三百多行代码,一行行读容易迷失,我们先将方法内的{}语句隐藏,从全局看这个方法都做了什么:

截屏2021-07-17 12.56.13

这个方法有个特点就是没执行完一个代码块都会执行ts.log()函数打印代码段都做了什么,这样子我们大体知道_read_images的主流程:

  • 条件控制进⾏⼀次的加载
  • 修复预编译阶段的 @selector 的混乱问题
  • 错误混乱的类处理
  • 修复重映射⼀些没有被镜像⽂件加载进来的类
  • 修复⼀些消息
  • 当我们类⾥⾯有协议的时候 : readProtocol
  • 修复没有被加载的协议
  • 分类处理
  • 类的加载处理
  • 没有被处理的类 优化那些被侵犯的类

first time tasks即:条件控制进⾏⼀次的加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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

# if TARGET_OS_OSX
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
// DisableNonpointerIsa = true;
// if (PrintRawIsa) {
// _objc_inform("RAW ISA: disabling non-pointer isa because "
// "the app is too old.");
// }
// }

// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section");
}
}
break; // assume only one MH_EXECUTE image
}
# endif

#endif

if (DisableTaggedPointers) {
disableTaggedPointers();
}
///初始化TaggedPointer混淆
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
// objc::unattachedCategories.init(32);
// objc::allocatedClasses.init();
//负载因子
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//创建一张类的总表,包含所有的类
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}

主要做了两件事情

  • 初始化小对象(TaggedPointer)
  • 创建所有类的总表,注意这个表和runtime_initallocatedClassess的表不一样,这里是所有类的表,而allocatedClassess是已经实现(allocated)的类的表。

fix up selector references即:修复预编译阶段的 @selector 的混乱问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) 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 sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
ts.log("IMAGE TIMES: fix up selector references");

selecotr是类的名字+地址。

  • SEL *sels = _getObjc2SelectorRefs(hi, &count);是从Mach-o文件读取的
  • SEL sel = sel_registerNameNoLock(name, isBundle);是从dyld链接之后获取的
  • 如果两个sel名字相等,地址不同就需要fix up

discover classes即:错误混乱的类处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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;
}
//从mach-o读取类
classref_t const *classlist = _getObjc2ClassList(hi, &count);

bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

for (i = 0; i < count; i++) {
//cls 目前没有名字
Class cls = (Class)classlist[i];
//关联类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");

修复未处理的将来的类。给类关联上名字。下面我们重点探索一下readClass:

readClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
///以下代码 为笔者添加 方便调试到自定义的类(因为所以类都会执行这里,我们只关心我们定义的类)
const char *customerClassName = "JSPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
//打印类名
printf("%s -: 要研究的类: - %s\n",__func__,mangledName);
}

if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->setSuperclass(nil);
return nil;
}

cls->fixupBackwardDeployingStableSwift();

Class replacing = nil;
if (mangledName != nullptr) {
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());
}

class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));

// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());

rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);

addRemappedClass(cls, newCls);

replacing = cls;
cls = newCls;
}
}
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.");
}
///添加到class表
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}

我们从返回值开始看cls,cls也是入参,经过readClass之后有了名字,主要作用就是对类及元类赋值名字并放入方法表中。

addNamedClass 添加方法名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);

// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));

// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}

addClassTableEntry

将类添加到表里面,如果addMeta为真,并且将当前类的元类也添加到所有的表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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

总结

本篇我们主要是对objc_init的流程做了一个简单的梳理,分析了readClass方法的作用,下一篇我们开始详细分析类的加载过程。