Category分析整理

1,050 阅读5分钟

[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中声明的方法,实现不一定在categoryimplement文件中,但是根据继承树找不到实现,会崩溃
  • 5、category方法中不可以调用super方法
  • 6、 如果多个category中的方法和原类重名,则编译器会执行最后一个参与编译的分类中的方法

三:Category的好处

  • 可以减少单个文件的体积
  • 可以把不同的功能组织到不同的category
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category

四:category局限性

  • category只能给某个已有的类扩充方法,不能扩充成员变量(可以运行时绑定)
  • category中也可以添加属性,只不过@property只会生成settergetter的声明,不会生成settergetter的实现以及成员变量
  • 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_
  • 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

五:apple推荐category的使用场景

  • 可以把类的实现分开在几个不同的文件里面
  • 声明私有方法
  • 模拟多继承
  • 把framework的私有方法公开

参考:深入理解Objective-C:Category


六:category、+load、+initialize

对比 +load +initialize
调用时机 被添加到 runtime 收到第一条消息前,可能永远不调用
调用方式 直接获取函数指针来执行,不会像 objc_msgSend 一样会有方法查找的过程 最终是通过 objc_msgSend 来执行
调用顺序 父类->子类->category 父类->子类
调用次数 1次 多次
是否需要显示调用父类实现
是否沿用父类实现
category中的实现 类和category都执行 覆盖类中的方法,只执行category的实现

参考:

Objective-C +load vs +initialize

你真的了解 load 方法么?

Category的本质<一>

Category的本质<二>load,initialize方法

Category的本质<三>关联对象

七:使用关联对象给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声明了一个属性,只是生成了setget方法,并没有实现,可以通过关联对象的方式实现
  • extension中声明的方法如果没有实现,编译器会报警告,但是category就不会有警告。
  • 因为extension是在编译阶段被添加到类中的,而category是在运行时添加到类中的(在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局)

为什么category不能添加成员变量?

category的类结构体可以看到有分类名name、类cls、实例方法列表instanceMethods、类方法类表classMethods、遵守的协议列表protocols、实例属性列表instanceProperties、类属性列表_classProperties,并没有成员变量列表。而且category是在运行期间添加的,此时对象的内存布局已经确定,如果添加实例变量会破坏类的内存布局,造成不同的对象内存布局不一致

注意:

关联对象的值不是存储在自己的实例对象结构中,而是维护了一个全局的AssociationMananger,存储在哈希表中。

九:参考:

iOS中category添加属性

Category批量添加属性:LcCategoryProperty

关联对象 AssociatedObject 完全解析

iOS分类(category),类扩展(extension)—史上最全攻略

深入理解Objective-C:Category