[TOC]
@(iOS开发学习)
一:Category结构体
typedef struct category_t {
const char *name; // 类的名字
classref_t cls; // 类
struct method_list_t *instanceMethods; // category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; // category中所有添加的类方法的列表
struct protocol_list_t *protocols; // category实现的所有协议的列表
struct property_list_t *instanceProperties; // category中添加的所有属性
} category_t;
二:使用注意事项
- 1、 即使没有引入
Category的头文件,Category的方法也会被添加进主类的方法列表里,可以通过performSelector的方式使用,导入头文件只是为了通过编译器的静态检查。- 2、
category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA- 3、
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法- 4、 因为附加
category到类的工作会先于+load方法的执行,所以在类的+load方法调用的时候,我们可以调用category中声明的方法,实现不一定在category的implement文件中,但是根据继承树找不到实现,会崩溃- 5、
category方法中不可以调用super方法- 6、 如果多个
category中的方法和原类重名,则编译器会执行最后一个参与编译的分类中的方法
三:Category的好处
- 可以减少单个文件的体积
- 可以把不同的功能组织到不同的
category里 - 可以由多个开发者共同完成一个类
- 可以按需加载想要的
category
四:category局限性
-
category只能给某个已有的类扩充方法,不能扩充成员变量(可以运行时绑定) -
category中也可以添加属性,只不过@property只会生成setter和getter的声明,不会生成setter和getter的实现以及成员变量 - 如果
category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_。 - 如果多个
category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。
五:apple推荐category的使用场景
- 可以把类的实现分开在几个不同的文件里面
- 声明私有方法
- 模拟多继承
- 把framework的私有方法公开
六:category、+load、+initialize
| 对比 | +load | +initialize |
|---|---|---|
| 调用时机 | 被添加到 runtime 时 |
收到第一条消息前,可能永远不调用 |
| 调用方式 | 直接获取函数指针来执行,不会像 objc_msgSend 一样会有方法查找的过程 |
最终是通过 objc_msgSend 来执行 |
| 调用顺序 | 父类->子类->category |
父类->子类 |
| 调用次数 | 1次 | 多次 |
| 是否需要显示调用父类实现 | 否 | 否 |
| 是否沿用父类实现 | 否 | 是 |
category中的实现 |
类和category都执行 |
覆盖类中的方法,只执行category的实现 |
参考:
Objective-C +load vs +initialize
七:使用关联对象给Category添加属性
@interface Person (Category)
@property (nonatomic, strong) NSString *name;
@end
#import "Person+Category.h"
#import <objc/runtime.h>
@implementation Person (Category)
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setName:(NSString *)categoryProperty {
objc_setAssociatedObject(self,
@selector(name),
categoryProperty,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
八:Category和Extension的区别
-
category的小括号中有名字,extension的小括号中没有名字(匿名category) -
category可以增加属性声明,但是不能增加实例 - 如果
category声明了一个属性,只是生成了set和get方法,并没有实现,可以通过关联对象的方式实现 -
extension中声明的方法如果没有实现,编译器会报警告,但是category就不会有警告。 - 因为
extension是在编译阶段被添加到类中的,而category是在运行时添加到类中的(在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局)
为什么category不能添加成员变量?
从
category的类结构体可以看到有分类名name、类cls、实例方法列表instanceMethods、类方法类表classMethods、遵守的协议列表protocols、实例属性列表instanceProperties、类属性列表_classProperties,并没有成员变量列表。而且category是在运行期间添加的,此时对象的内存布局已经确定,如果添加实例变量会破坏类的内存布局,造成不同的对象内存布局不一致。
注意:
关联对象的值不是存储在自己的实例对象结构中,而是维护了一个全局的AssociationMananger,存储在哈希表中。
九:参考:
Category批量添加属性:LcCategoryProperty