什么是category?
是在不改变已存在类的情况下,对其添加方法来达到对类进行功能扩展的目的。
category结构
可以在 objc-runtime-new.h 文件中看到 category_t 结构体的定义
struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //实例方法列表
struct method_list_t *classMethods; //类方法列表
struct protocol_list_t *protocols; //协议列表
struct property_list_t *instanceProperties; //实例属性列表
struct property_list_t *_classProperties; //类属性列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
复制代码
category 的实现原理
在源码 objc-runtime-new.mm 文件中的第 2560 行开始,可以看到关于 category 的处理逻辑:
for (EACH_HEADER) {
// 1.获取category列表
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 2.遍历
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
// 3.对应的类cls
Class cls = remapClass(cat->cls);
// 3.1 如果cls为nil
if (!cls) {
// 3.2 将category置为nil
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
bool classExists = NO;
// 4. 实例方法 或 协议 或 属性存在
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 4.1 处理category中的数据(类)
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 5. 类方法 或 协议 或 属性存在
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
// 5.1 处理category中的数据(元类)
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
复制代码
处理category中的数据又是在方法remethodizeClass()中实现,注意传入的cls参数,如果是实例方法则传入的是类cls,如果是类方法,传入的是cls的isa指针,也就是元类,跟踪该方法:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
// 1. 是否为元类
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// 2. 修改对应的类的方法列表、协议列表、属性列表
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
复制代码
修改对应的类或元类中的方法列表、协议列表、属性列表又是在attachCategories()方法内实现的,跟踪该方法:
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 1.创建数组
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
// 2. 倒叙插入
while (i--) {
auto& entry = cats->list[i];
// 2.1 方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 2.2 属性列表
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 2.3 协议列表
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount); // 插入方法列表
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount); // 插入属性列表
free(proplists);
rw->protocols.attachLists(protolists, protocount); // 插入协议列表
free(protolists);
}
复制代码
创建一个数组,倒叙插入 新增的方法、属性和协议、具体的插入实现在方法attachLists()中,跟踪该方法
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
// 1.主类中有多个数据集合的时候
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
// 1.1 使用realloc() 函数将原来的空间拓展
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 1.2 将原来的数组复制到后面
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 1.3 把新数组复制到前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
//2.为空的时候,直接将指针指向新的数据集。
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
// 3. 主类中只有一个数据集合的时候
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
// 3.1 使用malloc()重新申请一块内存
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
// 3.2 将原来的集合放到最后
if (oldList) array()->lists[addedCount] = oldList;
// 3.3 新数组复制到前边
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
复制代码
如上缩减,runtime 针对主类中原有列表的三种情况,有三种不同的插入处理。
小结
- 当category中方法和类中的方法同名的话,会优先调用category中的方法
- 这里看起来是category的方法覆盖了主类的方法,但实际是category的方法在方法列表的前面,所以总先调用category中的方法。
- 多个category中存在相同的方法时,调用的顺序跟编译的顺序有关,最后一个编译的,会被调用。
category为什么可以新增方法,而不能新增属性呢?
回头仔细看下 obj_class 的结构:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
/ ************** /
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
/ ************** /
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
注意看 ivars 和 methodLists 有什么区别?
ivars是指向 objc_ivar_list 的指针,而 methodLists 是指 向objc_method_list 的指针的指针。
objc_class 结构体的大小是固定的,不能添加数据,只能修改。 所以 ivars 指向的 objc_ivar_list 是一个固定区域,只能修改成员变量的值,不能增加成员变量的个数;
虽然没办法扩展methidLists指向的区域,但是可以修改 *objc_method_list 的值来增加成员变量;
给category增加属性 : 关联对象(Associated Object)
虽然可以实现,但是不推荐在category中添加属性
在 .h 文件中添加属性的声明
@property (copy, nonatomic) NSString * addName;
复制代码
在 .m 中
#import "NSObject+addProperty.h"
#import <objc/runtime.h>
@implementation NSObject (addProperty)
static char *addNameKey = "addNameKey";
-(void)setAddName:(NSString *)addName{
/* 关联方法:
* id object 给哪个对象的属性赋值
* const void *key 属性对应的key
* id value 设置属性值为value
* objc_AssociationPolicy policy 使用的存储策略,copy/retain/assign
*/
objc_setAssociatedObject(self, addNameKey, addName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)addName{
return objc_getAssociatedObject(self, addNameKey);
}
@end
复制代码
关联对象(Associated Object)
关联对象可以被理解为 Runtime 中的字典
objc_setAssociatedObject 相当于 setValue:forKey 进行关联value对象
关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的\
objc_getAssociatedObject 用来读取对象
objc_removeAssociatedObjects函数来移除一个关联对象
或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。