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中声明的属性,只会生成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() { ... }
@endNSString、NSArray、NSNumber等,因为系统本身不提倡使用继承去扩展方法,所以这些类内部实现对继承有所限制)protocol)framework的私有方法公开 程序启动装载类信息的时候(main函数之前,初始化runtime之后,加入runtime之前)仅调用一次,不会自动继承(复写也无需加[super load]),系统自动调用(无须手动调用)。
调用顺序:父类->子类->分类,类/不同的分类 的执行顺序,跟Targets->Build Phases->**Compile Sources**中出现的一致,前提要遵循父类先调用(所有Category的load在类的load之后调用)。
方法交换method swizzling;尽量不要在load方法里做耗时的操作,否则会增加app的启动时间,降低用户体验。
在该类第一次接收到消息之前(惰性)以线程安全(加锁)的方式调用,其他的消息会等待initialize完成。系统自动调用(无须手动调用)。
调用顺序:
[super initialize]) 如果想防止父类的initialize被调用多次,可以实现如下:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
} 因为initialize是以阻塞方式调用的,所以将initialize实现限制在所需的最小工作量是很重要的,特别是获取其他类需要的锁代码时,容易导致死锁!因此,不应该依赖initialize进行复杂的初始化。
可以做一些简单的初始化工作,如:初始化 全局变量 或 静态变量(整个类共用的数据);
1、Category和Extension是什么?两者的区别?
ivar、setter、getter2、为什么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(美团技术团队的文章) 深入详解 iOS的 +load和+initialize