category简介
category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景
可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,a)可以减少单个文件的体积 b)可以把不同的功能组织到不同的category里 c)可以由多个开发者共同完成一个类 d)可以按需加载想要的category 等等。
声明私有方法
category真面目
所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义)
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;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
成员变量
1)、类的名字(name)
2)、类(cls)
3)、category中所有给类添加的实例方法的列表(instanceMethods)
4)、category中所有添加的类方法的列表(classMethods)
5)、category实现的所有协议的列表(protocols)
6)、category中添加的所有属性(instanceProperties)
方法
method_list_t *methodsForMeta(bool isMeta)
根据传入是是不是元类来返回响应的值。
property_list_t *propertiesForMeta(bool isMeta)
这个就是判断是否是元类返回响应的属性,元类是没有属性的。
那么元类是什么?
看这个
代码重编译
我们先写个category 类,我们用clang -rewrite-objc xx.m 编译这个catergory
import <Foundation/Foundation.h>
@interface CategoryObject : NSObject
@end
@interface CategoryObject(MyAddition)
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *value;
- (void)printName;
-(void)printValue;
@end
@interface CategoryObject(MyAddition2)
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *age;
@property(nonatomic, copy) NSString *address; - (void)printName;
-(void)printAge;
-(void)printAddress;
@end
import "CategoryObject.h"
@implementation CategoryObject
- (void)printName
{
NSLog(@"%@",@"CategoryObject");
}
@end
@implementation CategoryObject(MyAddition)
- (void)printName
{
NSLog(@"printName %@",@"MyAddition");
}
-(void)printValue{
NSLog(@"printValue %@",@"MyAddition");
}
@end
@implementation CategoryObject(MyAddition2)
-(void)printAge{
NSLog(@"printAge %@",@"MyAddition2");
}
-(void)printAddress{
NSLog(@"printAddress %@",@"MyAddition2");
}
- (void)printName
{
NSLog(@"printName %@",@"MyAddition2");
}
@end
这里我们定义了两个category ,并且每一个category中都有方法和属性。
重新编译后的文件很多,我们就摘抄与我们有关的部分。
我们写的所有代码都是在文件最后面
static struct /_method_list_t/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} OBJC_MyAddition attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
{(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
};
static struct /_prop_list_t/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} OBJC_MyAddition attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@"NSString",C,N"},
{"value","T@"NSString",C,N"}}
};
extern "C" __declspec(dllexport) struct class_t OBJC_CLASS$_CategoryObject;
static struct _category_t OBJC_MyAddition attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASSCATEGORY_INSTANCE_METHODS_CategoryObjectPROP_LIST_CategoryObjectCategoryObjectCATEGORY_CategoryObject_CategoryObject;
}
static struct /_method_list_t/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} OBJC_MyAddition2 attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"printAge", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAge},
{(struct objc_selector *)"printAddress", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printAddress},
{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition2_printName}}
};
static struct /_prop_list_t/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[3];
} OBJC_MyAddition2 attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
3,
{{"name","T@"NSString",C,N"},
{"age","T@"NSString",C,N"},
{"address","T@"NSString",C,N"}}
};
extern "C" __declspec(dllexport) struct class_t OBJC_CLASS$_CategoryObject;
static struct _category_t OBJC_MyAddition2 attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASSCATEGORY_INSTANCE_METHODS_CategoryObjectPROP_LIST_CategoryObjectCategoryObjectCATEGORY_CategoryObject_CategoryObject;
}
pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooksCategoryObjectCategoryObject [1] attribute((used, section ("__DATA, _objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS [2] attribute((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&OBJC_MyAddition,
&OBJC_MyAddition2,
};
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
一点点看
static struct /_method_list_t/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} OBJC_MyAddition attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printName},
{(struct objc_selector *)"printValue", "v16@0:8", (void *)_I_CategoryObject_MyAddition_printValue}}
};
这里生成一个静态的struct 名字叫OBJCCATEGORYINSTANCEMETHODSCategoryObjectCATEGORYINSTANCEMETHODSCategoryObject_MyAddition,并且初始化该结构体;
这个结构体有三个变量
entsize 代表的是一个 struct _objc_method 的大小
method_count 代表category中有几个方法
method_list[2];是个数组,装的方法名字。
属性生成方式:
static struct /_prop_list_t/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[2];
} OBJC_MyAddition attribute ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"name","T@"NSString",C,N"},
{"value","T@"NSString",C,N"}}
};
属性也是和method 一样的生成方式
这个结构体有三个变量
entsize 代表的是一个 struct _prop_t 的大小
count_of_properties 代表category中有几个属性
prop_list[2];是个数组,装的属性
static struct _category_t OBJC_MyAddition attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASSCATEGORY_INSTANCE_METHODS_CategoryObjectPROP_LIST_CategoryObject$_MyAddition,
};
这里就是给_category_t 结构体赋值,结构体名字规则是 文件头 + 类名+ 类别名。不过这里的 classMethods 和 protocols 都是0 ,因为我们没有给类别增加这些东西。所以都是0.
static void OBJC_CATEGORY_SETUP__MyAddition(void ) {
OBJCMyAddition.cls = &OBJC_CLASS$_CategoryObject;
}
我们看category的 cls变量没有赋值。这里给出一个单独的函数对cls进行赋值。
static struct category_t *L_OBJC_LABEL_CATEGORYCATEGORY_CategoryObjectCATEGORY_CategoryObject$_MyAddition2,
};
这里保存一个_category_t 数组。
大概上面的代码看完了
编译器做了啥事情呢?
1)、首先编译器生成了实例方法列表
2)、其次,编译器生成了category本身
3)、最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$(当然,如果有多个category,会生成对应长度的数组_),用于运行期category的加载。比如上面这个例子 大小就为2
追本溯源
Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。
对于OC运行时,入口方法如下(在objc-os.mm文件中):
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
//环境初始化 读取影响运行时的环境变量 如果需要,还可以打印环境变量帮助。
environ_init();
tls_init();
//静态初始化 运行C++静态构造函数
static_init();
lock_init();
//异常初始化 初始化libobjc的异常处理系统 由map_images()调用
exception_init();
// Register for unmap first, in case some +load unmaps something
// 先注册取消映射,以防某些 +load 方法取消映射某些内容
//那么 +load方法什么情况下会被调用呢?
//load函数是只要你动态加载或者静态引用了这个类,那么load就会被执行,它并不需要你显示的去创建一个类后才会执行,同时只执行一次。
//另外就是关于load的执行顺序问题,所有的superclass的load执行完以后才会执行该类的load,以及class中的load方法是先于category中的load执行的。
_dyld_register_func_for_remove_image(&unmap_image);
dyld_register_image_state_change_handler(dyld_image_state_bound,
1/*batch*/, &map_2_images);
dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/not batch/, &load_images);
}
这个函数最终会调用到_read_images 方法中
那么 _read_images方法又是什么呢?
这个方法作用就是读取各个 section 中的数据并放到缓存中,这里的缓存大部分都是全局静态变量,载体就是我们一个c++map类的 hashmap
想深入了解看这个
怎么知道的呢?我们打断点调试下不就知道了。我们选择symbolic breakpoint 断点,进行调试
截图如下
_read_images方法调用:
这个_read_images方法中有有关category相关增加方法。
我们摘录相关部分
void _read_images(header_info **hList, uint32_t hCount)
{
···
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
···
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
···
// Fix up @selector references
···
// Discover protocols. Fix up protocol refs.
···
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
···
// Realize non-lazy classes (for +load methods and static instances)
···
// Realize newly-resolved future classes, in case CF manipulates them
···
// Discover categories.
for (EACH_HEADER) {
//1.获取category列表list
category_t **catlist =
_getObjc2CategoryList(hi, &count);
//2.遍历category list 中的每一个category
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
// 3.获取category 的cls.要是category 没有设置cls 就继续下一个
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category ???(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
//4.这里判断cat 是否有实例方法,协议或者属性。有的话就调用下
addUnattachedCategoryForClass 方法,判断cls 实现的话,就调用remethodizeClass 方法
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
//5.再判断category 是否有类方法或者协议。有的话也调用addUnattachedCategoryForClass 方法。在检测元类是否实现。调用下remethodizeClass 方法。
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
ts.log("IMAGE TIMES: discover categories");
···
// Category discovery MUST BE LAST to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
···
// Print preoptimization statistics
}
这个函数的基本结构就是这个样子。分的模块很明确
这里有两个关键方法addUnattachedCategoryForClass 和remethodizeClass 。
分别看
作用:为类添加未附加的类别 并通过NXMapInsert函数,更新所属类的Category列表
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
//1.调用unattachedCategories() 函数生成一个NXMapTable * cats,获取到未添加的Category哈希表 在这里cats 是全局对象,只有一个
//NXMapTable这个结构体的作用是:他是一个map类(哈希表)的变量,而map类允许散列任意关联[key->value]。键和值必须是指针或整数,客户端负责分配/解除分配此数据。提供了释放回调。
//作为性能良好的可伸缩数据结构,哈希表在开始变满时的大小将增加一倍,从而保证了平均恒定时间访问和线性大小
NXMapTable *cats = unattachedCategories();
//这个category_list 是用来存储所有的category的
category_list *list;
//2.我们从这个单例对象中查找cls ,获取一个category_list *list列表。
//cls:要扩展的类对象
list = (category_list *)NXMapGet(cats, cls);
//3 要是没有list 指针。那么我们就生成一个category_list 空间。
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
// 4.要是有list 指针,那么就在该指针的基础上再分配出category_list 大小的空间。
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
// 5.在这新分配好的空间,将这个cat 和catHeader 写入。
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
// 6.将数据插入到cats 中。key 是cls(要扩展的类对象) 值是list
NXMapInsert(cats, cls, list);
}
下面是unattachedCategories()实现方法
// 获取未添加到Class中的category哈希表
static NXMapTable *unattachedCategories(void)
{
// 未添加到Class中的category哈希表
runtimeLock.assertWriting();
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
//固定哈希表的初始内存大小 创建存储category的哈希表
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
这个哈希表的数据结构是这样子的:
一个全局map ,cls 是key value 是个list (相当于数组)
上面只是完成了向Category哈希表中添加的操作,这时候哈希表中存储了所有category_t对象。然后需要调用remethodizeClass函数,向对应的Class中添加Category的信息。
在remethodizeClass函数中会查找传入的Class参数对应的Category数组,然后将数组传给attachCategories函数,执行具体的添加操作。
// 将Category的信息添加到Class,包含method、property、protocol
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
isMeta = cls->isMetaClass();
// 从Category哈希表中查找category_t对象,并将已找到的对象从哈希表中删除
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
在attachCategories函数中,查找到Category的方法列表、属性列表、协议列表,然后通过对应的attachLists函数,添加到Class对应的class_rw_t结构体中。这个class_rw_t结构体就是存储类相关信息的一个结构体
// 获取到Category的Protocol list、Property list、Method list,然后通过attachLists函数添加到所属的类中
static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 按照Category个数,分配对应的内存空间
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));
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
// 循环查找出Protocol list、Property list、Method list
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
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);
}
这个过程就是将Category中的信息,添加到对应的Class中,一个类的Category可能不只有一个,在这个过程中会将所有Category的信息都合并到Class中。
方法覆盖找回
在有多个Category和原类的方法重复定义的时候,原类和所有Category的方法都会存在,并不会被后面的覆盖。假设有一个方法叫做method,Category和原类的方法都会被添加到方法列表中,只是存在的顺序不同。
在进行方法调用的时候,会优先遍历Category的方法,并且后面被添加到项目里的Category,会被优先调用。上面的例子调用顺序就是Category3 -> Category2 -> Category1 -> TestObject。如果从方法列表中找到方法后,就不会继续向后查找,这就是类方法被Category”覆盖”的原因。
两个问题
在有多个Category和原类方法重名的情况下,怎样在一个Category的方法被调用后,调用所有Category和原类的方法?
可以在一个Category方法被调用后,遍历方法列表并调用其他同名方法。但是需要注意一点是,遍历过程中不能再调用自己的方法,否则会导致递归调用。为了避免这个问题,可以在调用前判断被调动的方法IMP是否当前方法的IMP。
那怎样在任何一个Category的方法被调用后,只调用原类方法呢?
根据上面对方法调用的分析,Runtime在调用方法时会优先所有Category调用,所以可以倒叙遍历方法列表,只遍历第一个方法即可,这个方法就是原类的方法。
分类源码
网友评论