iOS底层原理 - 探寻Category本质 之 initialize

面试题引发的思考:

Q: loadinitialize方法的区别什么?它们在Category中的调用的顺序?以及出现继承时两者之间的调用过程?

load、initialize方法区别

1. Category中initialize方法

// TODO: -----------------  Person类  -----------------
@implementation Person
+ (void)initialize {
    NSLog(@"Person + initialize");
}
@end

// TODO: -----------------  Person+Category1类  -----------------
@implementation Person (Category1)
+ (void)initialize {
    NSLog(@"Person (Category1) + initialize");
}
@end

// TODO: -----------------  Person+Category2类  -----------------
@implementation Person (Category2)
+ (void)initialize {
    NSLog(@"Person (Category2) + initialize");
}
@end

// TODO: -----------------  Student类,继承Person类  -----------------
@implementation Student
+ (void)initialize {
    NSLog(@"Student + initialize");
}
@end

// TODO: -----------------  Student (Category1)类  -----------------
@implementation Student (Category1)
+ (void)initialize {
    NSLog(@"Student (Category1) + initialize");
}
@end

// TODO: -----------------  Student (Category2)类  -----------------
@implementation Student (Category2)
+ (void)initialize {
    NSLog(@"Student (Category2) + initialize");
}
@end

// TODO: -----------------  Teacher类,继承Person类  -----------------
@implementation Teacher

@end

// TODO: -----------------  ViewController类  -----------------
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [Teacher alloc];
    // objc_msgSend([Person class], @selector(alloc));
    // objc_msgSend([Teacher class], @selector(alloc));
    [Student alloc];
    // objc_msgSend([Person class], @selector(alloc));
    // objc_msgSend([Student class], @selector(alloc));
    [Person alloc];
    // objc_msgSend([Person class], @selector(alloc));
}

代码如上:

定义一个Person类、两个Person的分类,再定义Person的子类Student、两个Student的分类,然后分别实现initialize方法,最后定义Person的子类Teacher;不做任何操作,直接运行程序则没有打印。

接着放开[Teacher alloc];[Student alloc];[Person alloc];语句,运行程序打印结果:

// 打印结果
Demo[1234:567890] Person (Category2) + initialize
Demo[1234:567890] Person (Category2) + initialize
Demo[1234:567890] Student (Category2) + initialize

查看编译顺序:

编译顺序

通过以上代码分析即编译顺序可知:

  1. 第一句打印的是Person (Category2)类的initialize方法,代码执行的是[Teacher alloc];语句。
    说明[Teacher alloc];语句会先调用父类Personinitialize方法;

  2. 第二句打印的是Person (Category2)类的initialize方法,代码执行的是[Student alloc];语句。
    说明[Student alloc];语句会先调用父类Personinitialize方法;

  3. 第三句打印的是Student (Category2)类的initialize方法。
    说明[Student alloc];语句会先调用父类Personinitialize方法,再调用自己Studentinitialize方法;

  4. 第四句没有打印。
    说明[Person alloc];语句调用自己的initialize方法时,前面已经初始化过了;说明每个类只会初始化一次。


2. initialize方法调用原理

通过以上案例分析可得:

  • initialize方法在类第一次接收消息时调用;
  • 优先调用父类的initialize方法,再调用子类的initialize方法;
    (先初始化父类,再初始化子类,每个类只初始化一次)
    如果分类没有实现initialize方法,则会调用父类的initialize方法
    如果分类实现了initialize方法,就会覆盖类本身的initialize方法

3. initialize方法调用顺序

根据OC源码进行分析,首先找到Runtime函数class_getClassMethod

class_getClassMethod函数

然后通过class_getInstanceMethod读找到class_getInstanceMethod函数:

class_getInstanceMethod函数

然后通过lookUpImpOrNil读找到lookUpImpOrNil函数:

lookUpImpOrNil函数

然后通过lookUpImpOrForward读找到lookUpImpOrForward函数:

lookUpImpOrForward函数

源码显示:

  • 传入的类需要初始化且没有初始化,进行初始化操作,否则跳过;
  • 只有在第一次接收消息的时候调用initialize方法。

然后通过_class_initialize读找到_class_initialize函数:

_class_initialize函数

源码显示:

  • 如果传入的类存在父类且父类没有初始化,那么先初始化父类;
  • 然后再初始化自己,实现initialize方法。

4. Category中load方法、initialize方法比较:

Q: load方法、initialize方法的区别是什么?以及出现继承时两者之间的调用过程?

调用方式:

  • load是根据函数地址直接调用;
  • initialize是通过objc_msgSend调用。

调用时刻:

  • load 是Runtime加载类、分类的时候调用(只会调用一次);

  • initialize是类第一次接收到消息的时候调用,每个类只会调用initialize一次(父类的initialize可能调用多次)。

  • 调用顺序:

load

  • 先调用类的load方法;
    a> 按照编译的先后顺序调用;
    b> 调用子类的load方法之前会先调用父类的load方法;
  • 再调用分类的load方法。
    a> 按照编译的先后顺序调用

initialize

  • 先初始化父类;
  • 再初始化子类(可能最终调用的是父类的initialize 方法)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容