美文网首页
iOS 编译遇到的问题

iOS 编译遇到的问题

作者: 高浩浩浩浩浩浩 | 来源:发表于2024-01-17 21:09 被阅读0次

在XCode15.0 的版本使用中,发现了这样一个问题:

+ (void)load {
    static dispatch_once_t dbnOnceToken;
    dispatch_once(&dbnOnceToken, ^{
      Class clz = [self class];
      swizzleInstanceMethod(clz, @selector(custom_viewWillAppear:), @selector(lh_viewWillAppear:));
      swizzleInstanceMethod(clz, @selector(custom_viewDidAppear:), @selector(lh_viewDidAppear:));
      swizzleInstanceMethod(clz, @selector(custom_viewWillDisappear:), @selector(lh_viewWillDisappear:));
      swizzleInstanceMethod(clz, @selector(presentViewController:animated:completion:), @selector(new_presentViewController:animated:completion:));
  });
}

这个代码执行了, 但是在运行过程中,并没有完成方法的交换, 也就是说, 在执行custom_viewWillAppear: 方法的时候,执行的就是 custom_viewWillAppear :, 并没有被替换成 lh_viewWillAppear:

然后在TargetBuild Setting中, 把Other Linker Flags 添加-ld64以后,方法又可以被成功替换了。

好奇之下, 在这个方法中添加了打印信息:

NSLog(@"😧======方法交换 %@", NSStringFromClass(clz));
image.png

结果执行的时候发现这里打印的是:

😧======方法交换 那算咯 LMCACameraMainViewController

怎么会这么奇怪?!!!LMCACameraMainViewController这个又是哪来的?!!!
点开LMCACameraMainViewController这个类去看,发现这个LMCACameraMainViewControllerLMBaseViewController的子类, 子类为什么能替换掉父类的方法。
再看LMCACameraMainViewController这个类发现同样实现了 +load 方法,实现如下:

+ (void)load{
    [super load];
    ...
}

这里调用了[super load], 我们知道load 方法的调用顺序本身就是先执行父类, 再执行子类,这里应该是不需要自己手动调用 [super load]方法了。
于是注释掉这个[super load]方法,再次执行, 发现控制台打印正常了。

😧======方法交换 那算咯 LMBaseViewController

这里猜测可能是跟在编译阶段编译器的处理逻辑有关系, 在调用[super load] 方法以后,这个方法交换被转移到子类LMCACameraMainViewController中, 父类LMBaseViewController的方法并不会发生交换了。
LMBaseViewController 的load 方法中传入的指针指向的是子类,所以其他继承自LMBaseViewController 的类都不会执行 load方法了

Objc中的 +load 方法

  • +load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。
  • +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。
  • +load 方法是在所有类被加入到runtime以后调用的。
  • [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。
  • 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。
调用的顺序
  • +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。
  • [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。

这种现象是非常符合我们的直觉的,我们来分析一下这种现象出现的原因。
这是由于 schedule_class_load 有如下的实现:

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());

    if (cls->data()->flags & RW_LOADED) return;

    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED);
}

从这里可以看到,在传入一个cls 的时候,总是递归找到父类,并且加入到 loadable_classes 数组,从而确保其调用顺序的正确性。

  • 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。

这是因为类与分类中 load 方法的调用顺序主要在 call_load_methods 中实现:

do {
    while (loadable_classes_used > 0) {
        call_class_loads();
    }

    more_categories = call_category_loads();

} while (loadable_classes_used > 0  ||  more_categories);

上面的 do while 语句能够在一定程度上确保,类的 load 方法会先于分类调用。但是这里不能完全保证调用顺序的正确。

如果分类的镜像在类的镜像之前加载到运行时,上面的代码就没法保证顺序的正确了,所以,我们还需要在 call_category_loads 中判断类是否已经加载到内存中(调用 load 方法):

目前看来在Xcode15上面,编译时候load方法并不是严格按照这个顺序执行的,这里先调用了子类的load方法

这里有两个讲解load方法比较好的文章,可以看下

你真的了解 load 方法么?

相关文章

网友评论

      本文标题:iOS 编译遇到的问题

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