OC学习05:分类和扩展

张建 lol

分类 Category

  • Category 的主要作用是为 已经存在的类添加(扩展)方法

  • 已存在的类可以是 系统的类自定义的类

  • 不能定义 变量(成员变量或实例变量)

  • 一般情况下也可以定义 属性,但是 不会实现 它的 setget 方法,需要利用 runtime 机制去实现它的 setget 方法。

  • 分类文件有 .h.m 两个文件,文件名 @interface 类名 (分类名)

创建 系统 分类

  • command + n

  • 点击 Objective-C File

  • 创建完成后会生成 系统文件名+Category.h系统文件名+Category.m 两个文件

  • 在分类中可以添加 实例方法和类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface UIViewController (Cate)
- (void)sayHello;
+ (void)sayBye;
@end

==============

@implementation UIViewController (Cate)
- (void)sayHello{
NSLog(@"hello");
}
+ (void)sayBye{
NSLog(@"bye");
}
@end
  • 在本类中调用,需要引入头文件 文件名+Category.h
1
2
3
4
5
6
7
8
 // 分类
[self sayHello];
[ViewController sayBye];

======
Category[30386:1690552] bye
2023-03-06 22:06:37.369596+0800 Category[30386:1690552] hello
2023-03-06 22:06:37.369794+0800 Category[30386:1690552] bye

自定义的类 添加 分类

  • 创建方式类似

与系统类类似,在 class 中选择自定义的类 Person

可以在 分类中声明并实现 两个方法,在本类中调用

  • 分类中声明并实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface Person (Category)
- (void)sayHello;
+ (void)sayBye;
@end

---------

@implementation Person (Category)
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (void)sayBye{
NSLog(@"%s",__func__);
}
@end
  • 本类中调用,编译运行,查看打印结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation Person
- (instancetype)init{
self = [super init];
if (self) {
// 本类中调用分类添加的方法
[self sayHello];
[Person sayBye];
}
return self;
}
@end

============
2023-03-07 08:14:34.439456+0800 Category[3555:114948] -[Person(Category) sayHello]
2023-03-07 08:14:34.439626+0800 Category[3555:114948] +[Person(Category) sayBye]

那么问题来了,既然本类中可以调用,那么在其他类中是否也可以调用呢?

其他类中调用 分类 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 自定义的类
#import "Person.h"
#import "Person+Category.h"

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// 自定义的类
Person * p = [[Person alloc] init];
[p sayHello];
[Person sayBye];
}

========
2023-03-07 08:19:57.913133+0800 Category[3631:119871] -[Person(Category) sayHello]
2023-03-07 08:19:57.913667+0800 Category[3631:119871] +[Person(Category) sayBye]

由打印结果可知,在VC中时可以调用的

那么问题又来了,如果本类中和分类中均实现了 同样的方法 会调用哪个?

分类和本类实现了同样的方法

  • Person 类中实现一个 sayHello
1
2
3
- (void)sayHello{
NSLog(@"%s",__func__);
}
  • 在 VC 中调用 sayHello
1
2
3
4
5
6
// 自定义的类
Person * p = [[Person alloc] init];
[p sayHello];

============
2023-03-07 08:24:09.821814+0800 Category[3670:122944] -[Person(Category) sayHello]

由打印结果可知,调用的是 分类 的方法,由此我们可以推断,本类的方法被分类替代了

【总结】
普通方法的调用顺序?
1、只有父类和自己:调自己
2、如果有父类和自己和分类:只调用最后编译的分类

Category 的实现原理?Category 在编译过后,是在什么时机与原有的类合并到一起的?

  • 实现原理:

Category 实际上是 Category_t 的结构体,在运行时通过 Runtime,将 category 中的 方法、协议数据 放在 category_t 结构体 中,然后将结构体内的 方法列表拷贝到类对象的方法列表中。被以 倒序 插入到原有方法列表的最前面,所以不同的 Category,添加了同一个方法,执行的实际上是 最后一个不是覆盖

  • 时机:运行时

1)程序启动后,通过编译之后,Runtime 会进行初始化,调用 _objc_init
2)然后会 map_images镜像
3)接下来调用 map_images_nolock
4)再然后就是 read_images,这个方法会读取所有的类的相关信息。
5)最后是调用 reMethodizeClass:,这个方法是 重新方法化 的意思。
6)在 reMethodizeClass: 方法内部会调用 attachCategories: ,这个方法会传入 ClassCategory ,会将 方法列表、协议列表等与原有的类合并,最后加入到
class_rw_t 结构体中。

Category为什么只能加方法不能加成员变量?

  • Category 可以添加属性,但是并 不会自动生成成员变量及set/get方法。因为category_t 结构体中 并不存在成员变量。通过之前对对象的分析我们知道 成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而 分类 是在 运行时 才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

load、initialize的区别,以及它们在category重写的时候的调用的次序?

  • 区别在于调用方式和调用时刻

    • 调用方式:

      • load 是根据 函数地址 直接调用,

      • initialize 是通过 objc_msgSend 调用

    • 调用时刻:

      • loadruntime加载类、分类的时候调用

      • initialize类第一次接收到消息的时候调用

  • 调用顺序:

    • 父类的load > 类load > 分类中load(多个分类:先编译的分类优先调用load方法)。

    • 先调父类initialize -> 再调子类initialize。

    • 如果有分类:父类initialize -> 分类+initialize,分类覆盖类的+initialize。

普通方法的调用顺序?

  • 只有父类和本类:调 本类

  • 如果有父类和本类和分类:只调用最后编译的 分类

扩展 Extension

  • 是类的一部分,在编译器和头文件里的@interface一级实现文件里的@implement一起形成一个完整的类,它伴随着类的产生而产生,亦随之一起消亡

作用

  • 声明 私有属性、私有方法、私有成员变量,想要被访问 @public
  • 扩展不能为系统类添加扩展(必须有一个类的源码才能添加一个类的Extension),所以你无法为系统的类添加Extension
  • 成员变量和实例变量是 私有的,不能被外界访问,
  • 属性可以被外界访问,由于没有实现调用会 carsh
  • 方法可以被外界访问,由于没有实现set和get方法,访问会 carsh
  • 扩展只有一个 .h 文件,因此 所有的都是没办法调用

使用

  • 创建 Extension

  • 自定义类中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
@interface Person : NSObject

@end

========
#import "Person.h"
#import "Person+Extension.h"

@implementation Person
- (void)sayHello{
NSLog(@"%s hello",__func__);
}
@end
  • #import "Person+Ext.h" 中实现如下代码
1
2
3
4
5
6
7
8
9
@interface UIViewController ()
{
NSString * name;
@public int age;
}
@property (nonatomic,copy)NSString * sex;
- (void)sayHello;
+ (void)sayBye;
@end
  • VC 中调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
[super viewDidLoad];

Person * p = [Person new];
// 调用报错或崩溃
// p->age = 30;
// p->name = "ZJ";
// p.sex = @"男";
// [Person sayBye];

[p sayHello];
}

=======
2023-03-06 22:35:02.277867+0800 Extension[30580:1704777] -[Person sayHello] hello

如果只有声明没有实现,则调用会 Crash

分类 和 扩展的区别

  • category 在运行时决议。extension 在编译时决议。所以扩展中的方法没有被实现编译器会报警告,分类中没有被实现编译器不会警告

  • 分类原则上只能添加方法,不能添加属性(因为没有实现属性的seter和getter方法,可以通过runtime添加)。扩展能添加方法、实例变量,默认是@private类型的,且只能作用于自身类

  • 分类有自己的实现部分。扩展没有实现部分,只能依托对应的类的实现部分。

  • 分类可以为系统添加分类。扩展不能为系统添加分类。

  • Post title:OC学习05:分类和扩展
  • Post author:张建
  • Create time:2023-03-06 21:50:50
  • Post link:https://redefine.ohevan.com/2023/03/06/OC/OC学习05:分类和扩展/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.