OC中Category到底是怎么回事?
前言
- 发布此文章主要是对自己所学知识的总结
- 通过文章的方式可以让自己对所学知识加深印象
- 方便日后需要的时候查看,如果有不对的地方欢迎指出
- 文笔不行,多多见谅
一直都是用印象笔记来记录自己的学习过程,但是笔记做完很久不看的话,就会忘记很多细节,所以才会选择再写一遍,加深一下印象,书读百遍其义自见.这段时间没有新需求,利用空闲时间巩固一下知识,免得空度时光
前面几篇中分别对对象和类的结构,以及消息发送转发的流程进行了解说,今天就和大家分享一下Category
,虽然网上已经有很多这样的文章,但人家的终归是人家的,只看不写印象还是不会深刻
一 : Category能干点啥?需要注意什么?
分类的作用:
- 在不改变原来类的基础上,让类可以有更多的功能
- 可以减少单个文件的体积,一个类可以分成很多模块,每个分类负责一个模块,不用把代码全堆到一个地方,方便管理,代码清晰
分类的使用注意:
- 分类只能添加方法,不能添加成员变量;
- 在分类中可以去访问本类中的成员变量
- 可以重写本类的方法,记住一点
尽量不要去重写系统类的方法
,很容易出现问题 - 可以为分类添加属性,不过需要手动set,get方法进行对象关联
二 : Category的内部结构
Category内部长啥样啊?源码中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;
// Fields below this point are not always present on disk.从这个位置往下的几个字段不会总是出现在磁盘上,先不用去管它了
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);
};
先不解释上面字段的意思,我们通过clang
编译成c++代码后看看再进行分析
先定义一个Person类,添加一个Person+TestOne的分类,只添加了一个doSomeThing
的实例方法,代码就不贴了
编译成c++代码如下:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
static struct _category_t _OBJC_$_CATEGORY_Person_$_TestOne __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_TestOne,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_TestOne,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_TestOne,
0,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_TestOne(void ) {
_OBJC_$_CATEGORY_Person_$_TestOne.cls = &OBJC_CLASS_$_Person;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_Person_$_TestOne,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Person_$_TestOne,
};
从上面代码可以看出这个分类就是一个_category_t
结构体指针,结构体名字是_OBJC_$_CATEGORY_Person_$_TestOne
,如果再创建一个Person的分类test
,它的名字和上面一个就是把TestOne
换成test
,
_OBJC_$_CATEGORY_Person_$_TestOne
结构体初赋值操作,对应的是_category_t
里面的字段
_category_t
中字段的意义:
name
: 类名
cls
: 所属类,&OBJC_CLASS_$_Person
instance_methods
: 对象方法列表
class_methods
: 类方法列表
protocols
: 协议列表
properties
: 属性列表(这个地方我没有写属性,所以赋值的时候为0)
接下来先看看方法列表(实例方法和类方法)
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_TestOne __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"doSomeThing", "v16@0:8", (void *)_I_Person_TestOne_doSomeThing}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_TestOne __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_Person_TestOne_classMethod}}
};
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
method_list_t
: 内部包括,方法占用内存大小,列表中发方法数量,方法的IMP,参数,SEL
小节一下 : 分类也是一个结构体,内部保存了类的名字,方法列表,协议列表,属性列表,但是没有成员变量之类的东西,所以上面说分类中是不能添加成员变量的
三 : Category源码实现
在objc_os.mm中搜索_objc_init
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();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
& map_images
:处理dyld映射来的数据
在map_images_nolock
中找到读取映射数据的函数_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];//从二维数组中取出第i个元素,也是一个数组,数组元素是category_t *cat类型的
Class cls = remapClass(cat->cls);//获取已经重新组织的类
//如果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;
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" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && 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);
}
}
}
}
以上代码主要作用:
- 把Category中的实例方法,协议,属性合并到类中
- 把Category中类方法合并到这个类的元类中
接下来我们看一下分类的数据是怎么合并到类中的,主要代码在remethodizeClass
这个函数中的attachCategories
static void remethodizeClass(Class cls)
{
category_list *cats;
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 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;
while (i--) {
auto& entry = cats->list[i];//遍历拿到每一个分类
//将分类中的方法存储到mlist中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//将分类中的属性存储到proplists中
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//将分类中的协议存储到protolists中
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();//获取class中的class_rw_t结构体
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);
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
/*
addedLists :传入的分类的方法列表,协议列表或者属性列表
array()->lists 原来的方法列表,协议列表或者属性列表
memmove:内存移动
memcpy:内存拷贝
把分类的数据移动到原来数组的第一位,
*/
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
通过上面的代码,把分类的方法放在了类的最前面,这也就验证了分类的方法不会覆盖原来类的方法,只是每次方法调用的时候,会在方法列表中查找,而分类的方法在最前头,找到了以后就直接返回了,不会再继续执行!这也是为什么分类和原来类都有相同的方法,却只会调用分类的方法的原因
源码不能运行,很多地方也只是猜测,只需要大概知道一下流程就行,没必要去研究每一行的代码是什么意思
后面的文章会说一下load 和 initialize在分类中的调用顺序,敬请期待...
本次分享到此为止,天天学习,好好向上!!