iOS底层系列之<6>--Category本质、load、initialize

1、Category的基本使用

  • 当业务比较复杂,需要将部分业务 分模块写出来,可以用分类
// Person.h文件
#import <Foundation/Foundation.h>
#import "Cat.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject
- (void)run;
@end

NS_ASSUME_NONNULL_END
--------------------------------------------------
// Person.m文件
#import "Person.h"

@implementation Person
- (void)setAge:(int)age {
    NSLog(@"调用方法:%s %d",__func__,age);
}
- (void)run {
    NSLog(@"run");
}
@end
// sleep分类
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (sleep)
- (void)sleep;
@end

NS_ASSUME_NONNULL_END


#import "Person+sleep.h"

@implementation Person (sleep)
- (void)sleep {
    NSLog(@"sleep");
}
@end

// eat分类
#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (eat)
- (void)eat;
@end

NS_ASSUME_NONNULL_END

------------------------------------------------
#import "Person+eat.h"

@implementation Person (eat)
- (void)eat {
    NSLog(@"eat");
}
@end

// 简单调用
- (void)testCategory {
    self.person = [[Person alloc] init];
    [self.person run];
    [self.person sleep];
    [self.person eat];
}

2、Category的本质

  1. 通过runtime加载某个类的所有Category数据

  2. 把所有Category的方法,属性,协议数据,合并到一个大数组中。
    注意编译顺序:后参与编译的Category数据,会在数组的前面,后面会先调用。

  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。反过来说,原来的类的数据会最后调用。

  4. 如果分类中含有和原来的类方法名相同的,就肯定会先调用分类的方法,而不会调用原来的类的方法。

在这里插入图片描述

3、+ load方法

  • + load方法会在runtime加载类、分类时调用

  • 每个类、分类的+load,在程序运行过程中只调用一次

  • 调用顺序
    \1. 先调用类的 +load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会先调用父类的+load
    \2. 再调用分类的 +load
    按照编译先后顺序调用(先编译,先调用)

  • 一般的方法[Object method]调用的本质是通过runtime的消息转发机制,进行调用的,所以会被分类覆盖

  • 而+load方法是系统自动调用的,不是手动[Object method]调用的,通过函数指针,直接找到对应的内存地址进行调用
    在这里插入图片描述

4、+initialize方法

  • +initialize方法会在类第一次接收到消息时调用
  • 调用顺序
  1. 先调用父类的+initialize,再调用子类的+initialize
[Person alloc]
// 第一次接受到消息的时候,就会调用initialize
// 伪代码
objc_msgSend([SuperClass class], @selector(alloc));
objc_msgSend([Person class], @selector(alloc));

// 所以说,先调用父类的+initialize,再调用子类的+initialize
  • +initialize和+load很大的区别是,+initialize是通过objc_msgSend进行调用,所以有以下特点:
    1.如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
// JHPerson 和 JHTeacher 都是 Person 的子类,并且只有Person这个父类实现了+initialize方法
- (void)testInit {
    NSLog(@"--------");
    [JHPerson alloc];
    [JHPerson alloc];
    [JHTeacher alloc];
    [JHTeacher alloc];
}

// 打印结果:
2021-12-29 10:10:49.551502+0800 02-kvc[64187:3137661] --------
2021-12-29 10:10:49.551606+0800 02-kvc[64187:3137661] +[Person initialize]
2021-12-29 10:10:49.551677+0800 02-kvc[64187:3137661] +[Person initialize]
2021-12-29 10:10:49.551768+0800 02-kvc[64187:3137661] +[Person initialize]
  • 分析,为什么会打印这样的结果呢?
// 伪代码
- (void)testInit {
    NSLog(@"--------");
    [JHPerson alloc];
    [JHPerson alloc];
    [JHTeacher alloc];
    [JHTeacher alloc];

 	// 伪代码逻辑
 	if (JHPerson没有初始化) {
 		if (JHPerson的父类Person没有初始化) {
			objc_msgSend([Person class], @selector(initialize)); // 第一次调用父类
		}
		objc_msgSend([JHPerson class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第二次调用父类
 	}
 	
    if (JHTeacher没有初始化) {
 		 if (JHTeacher的父类Person没有初始化) { // 父类已经初始化了,不会进来
		    	objc_msgSend([Person class], @selector(initialize));
		  }
		   objc_msgSend([JHTeacher class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第三次调用父类
 	   }
}
  • 根据以上分析,所以会最终调用3次initialize方法

2.如果分类实现了+initialize,就覆盖类本身的+initialize调用
(因为是消息机制,所以第2点很容易理解,也不是真正意义上的覆盖,而是先去调用了分类的+initialize,就不会再继续寻找类本身的+initialize了)

5、load、initialize方法的区别总结

  1. 调用方式
    1> load是根据函数地址直接调用
    2> initialize是通过objc_msgSend调用

  2. 调用时刻
    1> load是runtime加载类、分类的时候调用(只会调用一次)
    2>initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次)

  3. 在类和分类、子类中,load、initialize的调用顺序?
    一、 load
    1>先调用类的load
    (a)先编译的类,优先调用load
    (b) 调用子类的load之前,会先调用父类的load
    2>再调用分类的load
    (a) 先编译的分类,优先调用load

    二、 initialize
    1> 先初始化父类
    2>再初始化子类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值