文章目录
Extension、Category、load、initialize
一、Extension 延展
定义:
Extension(也叫:扩展/延展/匿名分类),可以声明属性、方法和成员变量。
创建Extension的文件的话,只会生成一个.h
文件,或者可以寄生于类的.m
文件中。如:
// MOPerson_xxx.h 或 MOPerson.m
@interface MOPerson()
@property (nonatomic, copy, readwrite) NSString *name;
@end
使用:
一个属性可以在.h
文件中声明为只读的,在.m
文件的Extension中声明为可写的,从而实现对数据的保护。
二、Category 类别
定义:
Category(也叫:类别/分类/类目),无需继承即可为类新增方法和协议,不需要获取源代码。
Category的名字不能重复,否则会报错
如果与原有类方法重名:在方法列表中Category的方法会排在类原有方法的前面,从而有“覆盖”了原类方法的错觉。(所以尽量不起同名的方法,除非是故意想覆盖)
Category中声明的属性,只会生成setter
和getter的声明,不会实现setter
、getter和成员变量
如:
// MOPerson+Fitness.h
@interface MOPerson (Fitness)
- (void)fit_run();
- (void)fit_swim();
@end
// MOPerson+Fitness.m
@implementation MOPerson (Fitness)
- (void)fit_run() { ... }
- (void)fit_swim() { ... }
@end
使用:
- 当一个大型的类功能繁多时,可以将不同的功能分在不同的分类中实现,进而可以按需引入不同的Category,优化代码;有助于提高可维护性,简化单个文件的管理
- 当需要扩展系统类时。(如:
NSString
、NSArray、NSNumber
等,因为系统本身不提倡使用继承去扩展方法,所以这些类内部实现对继承有所限制) - 模拟多继承(另外可以模拟多继承的还有
protocol
) - 把
framework
的私有方法公开
三、+load
定义:
程序启动装载类信息的时候(main函数之前,初始化runtime
之后,加入runtime
之前)仅调用一次,不会自动继承(复写也无需加[super load]
),系统自动调用(无须手动调用)。
调用顺序:父类->子类->分类,类/不同的分类 的执行顺序,跟Targets
->Build Phases->**Compile Sources
**中出现的一致,前提要遵循父类先调用(所有Category的load
在类的load
之后调用)。
使用:
方法交换method swizzling
;尽量不要在load方法里做耗时的操作,否则会增加app的启动时间,降低用户体验。
四、+initialize
定义:
在该类第一次接收到消息之前(惰性)以线程安全(加锁)的方式调用,其他的消息会等待initialize完成。系统自动调用(无须手动调用)。
调用顺序:
- 父类的会比子类的先执行(所以复写也无需加
[super initialize]
) - 子类未实现:调用父类的(继承思想),在此之前父类的已经被调用了一次,所以父类的可能会被调用多次
- 分类实现了:调用分类的(会覆盖当前类的)
使用:
如果想防止父类的initialize被调用多次,可以实现如下:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
因为initialize是以阻塞方式调用的,所以将initialize实现限制在所需的最小工作量是很重要的,特别是获取其他类需要的锁代码时,容易导致死锁!因此,不应该依赖initialize进行复杂的初始化。
可以做一些简单的初始化工作,如:初始化 全局变量 或 静态变量(整个类共用的数据);
五、灵魂拷问
1、Category和Extension是什么?两者的区别?
- Category有名字,Extension没有
- Category声明的属性,不会自动生成
ivar
、setter
、getter
- Extension可以添加实例变量,Category不可以
- Extension在编译时,其数据就包含在类信息中;Category在运行时,才会将数据合并到类信息中
- Extension不能像Category那样拥有独立的**@implementation部分。也就是说Extension声明的方法必须依托对应类的@implementation**部分实现。
2、为什么Category可以添加属性和方法,却不能添加成员变量?
Class
结构体如下:
struct objc_class {
Class isa;
...
struct objc_ivar_list *ivars; // 成员变量链表
struct objc_method_list **methodLists; // 方法定义的链表
struct objc_cache *cache; // 方法缓存
struct objc_protocol_list *protocols; // 协议链表
} OBJC2_UNAVAILABLE;
Category
结构体如下:
typedef struct category_t {
const char *name; // class类名
classref_t cls; // 类对象地址
struct method_list_t *instanceMethods; // 对象方法
struct method_list_t *classMethods; // 类方法
struct protocol_list_t *protocols; // 协议
struct property_list_t *instanceProperties; // 属性
} category_t;
现象:添加成员变量,会报错:build failure
原因:类的内存布局在编译时期就已经确定了,而Category是由runtime(运行时)才加载的。分类不是类没有自己的
isa,只会将自己的
methodattach
到主类,并不会影响到主类的
IvarList`。
ivars
是指向名为objc_ivar_list的结构体
的指针(指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数);而methodLists是一个指针,它指向另一个指针,另一个指针指向名为objc_method_list
的结构体(可以修改另一个指针,即*methodLists
的值来增加成员方法,虽不能扩展methodLists
指向的内存区域,却可以改变这个内存区域的值);Runtime
时objc_class结构体的大小是固定的,不能往其中添加数据,只能修改。
虽然说runtime
有一个 lass_addIvar()
添加成员变量的方法,但是只能在“构建一个类的过程中”调用。一但完成类定义,就不能再添加成员变量了。而分类是在运行时才加载,所以分类不能add
3、继承Inherit
和分类Category
在实现中有和区别?(耦合度)
分类Category:
- 允许开发者在不改动原有类的情况下,对该类进行扩展使用,是对一个功能完备的类的一种补充;
继承Inherit:
- 耦合度比较高,依赖父类,要求对父类的工作流程相对熟悉;
- 如果继承体系太复杂会导致整个系统混乱难以维护。
- 当需要扩展的方法与原方法同名时,并且需要调用父类的同名方法,则需要用继承;(因为分类的同名方法会覆盖原方法的实现,进而访问不到原方法。)
4、系统是怎么实现Category的?Category是如何附加到主类上面的?
Category的实现结构上文有给出,作用时机:runtime
初始化时,会查询分类,合并分类的方法、属性、协议等,并将分类的东西和原类的合并到一起(把原类的东西后移,将分类的放在前面)。
具体过程可以看这篇文章:iOS底层原理总结 - Category的本质
5、Category为什么只能加方法,而不能加属性?
可以添加属性,只是系统不会自动为Category中的属性实现setter
和getter
方法;因为不能添加实例变量,所以需要通过runtime
动态绑定的方式,实现setter
和getter方法。
6、Category有load方法吗?load方法是什么时候调用的?load方法能继承吗?
Category有load方法,load方法在程序启动装载类信息的时候(main
函数之前,初始化runtime之后)调用,仅调用一次。不会自动继承,但是可以继承(自己在Category的load方法里,调用父类的load方法/子类的load方法;但感觉还是没必要,因为在此之前它们都会被调用,还是看具体需求吧~)
7、method swizzling方法交换写在load还是initialize里?为什么?
写在load,程序启动加载类信息的时候调用,仅调用一次。如果写在initialize,可能会被调用多次,或者一次都没调用。为了安全起见,在load中实现method swizzling
也要做唯一性判断:(如:防止子类有调用[super load]
的情况)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// doSomething
});
}
参考:
官网文档:load()、initialize()
iOS底层原理总结 - Category的本质 (源码底层实现,数据结构,怎么attach到原类上)
深入理解Objective-C:Category(美团技术团队的文章)