什么是Category?
- Category是Objective-C 2.0之后添加的语言特性,分类、类别其实都是指的Category。
- Category的主要作用是为已经存在的类添加方法。也可以说是将庞大的类代码按逻辑划入几个分区。
- 分类的特性是可以在运行时阶段动态的为已有的类添加新行为。
- Objective-C 中Category 就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。
分类和扩展
- 扩展(Extension)有时候被称为匿名分类。但是两者实质上不是一个内容。
- 扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的
@implementation
中实现。所以这也就意味着我们无法对系统的类使用扩展。 - 同时与分类不同,扩展不但可以声明方法,还可以声明成员变量,这是分类所做不到的。
Category的实质
Category结构体
typedef struct category_t *Category;
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);
};
复制代码
分类的结构体中可以为类添加对象方法、类方法、协议、属性,但是并没有成员变量
从C++开始看起
我们将分类的.m文件转成c++文件来了解一下
我们先搞个分类
@interface NSObject (testCategory) <TestProtocol>
@property (nonatomic, copy) NSString *personName;
- (void)test;
+ (void)secondTest;
@end
复制代码
声明一个实例方法、一个类方法、一个属性
分类遵循一个协议 协议里也是一个类方法和对象方法
@protocol TestProtocol <NSObject>
- (void)protocolMethod;
+ (void)protocolClassMethod;
@end
复制代码
clang -rewrite-objc NSObject+testCategory.m
转成C++
【category结构体】
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;
};
【category结构体赋值】
static struct _category_t _OBJC_$_CATEGORY_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"NSObject",
0, // &OBJC_CLASS_$_NSObject,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_testCategory,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_testCategory,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_NSObject_$_testCategory,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_NSObject_$_testCategory,
};
【结构体数组】
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_NSObject_$_testCategory,
};
复制代码
- 我们可以看到重点的三个元素
-
- category结构体
-
- category结构体的赋值语句
-
- category结构体数组
对象方法列表结构体
【对象方法的实现】
static void _I_NSObject_testCategory_test(NSObject * self, SEL _cmd) {
printf("正在打印test方法");
}
static void _I_NSObject_testCategory_protocolMethod(NSObject * self, SEL _cmd) {
printf("正在打印协议对象方法");
}
【对象方法列表结构体】
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_NSObject_testCategory_test},
{(struct objc_selector *)"protocolMethod", "v16@0:8", (void *)_I_NSObject_testCategory_protocolMethod}}
};
复制代码
- (void)test
和- (void)protocolMethod
方法的实现- 对象方法结构体列表结构体
只要是在Category中实现了的对象方法(包括代理中的对象方法)。都会添加到对象方法列表结构体_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_testCategory
中来,如果仅仅是定义,没有实现,不会加进来
类方法列表结构体
【类方法的实现】
static void _C_NSObject_testCategory_secondTest(Class self, SEL _cmd) {
printf("正在打印secondTest方法");
}
static void _C_NSObject_testCategory_protocolClassMethod(Class self, SEL _cmd) {
printf("正在打印类对象方法");
}
【类方法列表结构体】
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"secondTest", "v16@0:8", (void *)_C_NSObject_testCategory_secondTest},
{(struct objc_selector *)"protocolClassMethod", "v16@0:8", (void *)_C_NSObject_testCategory_protocolClassMethod}}
};
复制代码
+ (void)secondTest
和+protocolClassMethod
类方法的实现- 类方法列表结构体
只要是Category中实现了的类方法(包括代理中的类方法)。都会添加到类方法列表结构体_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_testCategory
中来
协议列表结构体
【协议结构体】
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
【分类中添加的协议列表结构体】
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];//为什么是1?
} _OBJC_PROTOCOL_REFS_TestProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
【协议列表 对象方法列表结构体】
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_INSTANCE_METHODS_TestProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"protocolMethod", "v16@0:8", 0}}
};
【协议列表 类方法列表结构体】
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_PROTOCOL_CLASS_METHODS_TestProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"protocolClassMethod", "v16@0:8", 0}}
};
【结构体赋值】
struct _protocol_t _OBJC_PROTOCOL_TestProtocol __attribute__ ((used)) = {
0,
"TestProtocol",
(const struct _protocol_list_t *)&_OBJC_PROTOCOL_REFS_TestProtocol,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_TestProtocol,
(const struct method_list_t *)&_OBJC_PROTOCOL_CLASS_METHODS_TestProtocol,
0,
0,
0,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_TestProtocol
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_TestProtocol = &_OBJC_PROTOCOL_TestProtocol;
复制代码
- 协议列表结构体
- 协议列表 对象方法列表结构体
- 协议列表 类方法列表结构体
- 结构体赋值语句
属性列表结构体
struct _prop_t {
const char *name;
const char *attributes;
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_NSObject_$_testCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"personName","T@\"NSString\",C,N"}}
};
复制代码
从【属性列表结构体】源码中我们可以看到:只有person分类中添加的属性列表结构体_OBJC_$_PROP_LIST_NSObject_$_testCategory
,没有成员变量结构体_ivar_list_t
结构体。更没有对应的set/get
方法相关的内容。
这也说明了Category中不能添加成员变量这一事实。
category总结
主要包含下面几部分内容
- _method_list_t 类型的【对象方法列表结构体】;
- _method_list_t 类型的【类方法列表结构体】;
- _protocol_list_t 类型的【协议列表结构体】;
- _prop_list_t 类型的【属性列表结构体】。
_category_t
结构体中并不包含_ivar_list_t
类型,也就是不包含【成员变量结构体】
分类在运行期做了什么
想搞明白这个问题,我们就要知道什么时候调用了分类的方法
_objc_init
这个函数是runtime的初始化函数,我们从_objc_init
开始入手
_objc_init
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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读取资源(images代表资源模块),来到map_images_nolock
函数中找到_read_images
函数,在_read_images
函数中找到与分类相关的代码
_read_images
// 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];
Class cls = remapClass(cat->cls);
if (!cls) {
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;
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
列表list - 遍历
category list
中的每一个category
- 获取
category
的对应的主类cls,如果没有cls就跳过(continue)这个继续获取下一个 - 如果其有对应的主类,并其有实例方法、协议、属性,则调用
addUnattachedCategoryForClass
,同时如果cls中有实现的话,进一步调用remethodizeClass
方法 - 如果其有对应的主类,并其有类方法、协议,则调用
addUnattachedCategoryForClass
,同时如果cls的元类有实现的话,就进一步调用remethodizeClass
方法
4、5主要是分类对应的主类是元类对象还是类对象
看一下对应的addUnattachedCategoryForClass的方法
addUnattachedCategoryForClass
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
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
//全局对象
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
复制代码
- 通过
unattachedCategories()
函数生成一个全局对象cats
- 我们从这个单例对象中查找
cls
,获取一个category_list *list
列表 - 要是没有
list
指针。那么我们就生成一个category_list
空间 - 要是有
list
指针,我们就在该指针的基础上再分配处category_list
大小的空间 - 在这新分配好的空间,将这个
cat
和catHeader
写入 - 将数据插入到
cats
中,key—–>cls value——>list
这段代码对于我们来说,对分类的实现部分关系并不大,其仅仅是把类和category做一个关联映射,而remethodizeClass
才是真正去处理添加事宜的功臣
remethodizeClass
static void remethodizeClass(Class cls)
{
//分类数组
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
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)" : "");
}
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();
// 创建方法列表、属性列表、协议列表,用来存储分类的方法、属性、协议
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; // 记录是否是从 bundle 中取的
while (i--) { // 从后往前依次遍历
auto& entry = cats->list[i]; // 取出当前分类
// 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; // 将方法列表放入 mlists 方法列表数组中
fromBundle |= entry.hi->isBundle(); // 分类的头部信息中存储了是否是 bundle,将其记住
}
// 取出分类中的属性列表,如果是元类,取得的是 nil
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;
}
}
上面部分的代码仅仅是将分类中的方法、属性、协议插入到各自对应的大数组中
注意是从后往前加入的(为啥???)
--------------
// 取出当前类 cls 的 class_rw_t 数据
auto rw = cls->data();
// 存储方法、属性、协议数组到 rw 中【注意是rw哦】
// 准备方法列表 mlists 中的方法【为什么需要准备方法列表这一步?】
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 将新方法列表添加到 rw 中的方法列表中
rw->methods.attachLists(mlists, mcount);
// 释放方法列表 mlists
free(mlists);
// 清除 cls 的缓存列表
if (flush_caches && mcount > 0) flushCaches(cls);
// 将新属性列表添加到 rw 中的属性列表中
rw->properties.attachLists(proplists, propcount);
// 释放属性列表
free(proplists);
// 将新协议列表添加到 rw 中的协议列表中
rw->protocols.attachLists(protolists, protocount);
// 释放协议列表
free(protolists);
}
复制代码
- 先创建方法列表、属性列表、协议列表的新列表并且给它们分配内存,然后存储该cls所有的分类的方法、属性、协议,然后转交给了attachMethodLists方法(就是后面的那些写在一起了)
为什么需要准备方法列表这一步呢?
方法的查找算法是通过二分查找算法,说明sel-imp是有排序的,那么是如何排序的呢?
perpareMethodLists中主要调用了fixup方法
在 fixupMethodList 方法中会遍历 mlist,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist 进行重新排序。
这也就意味着 remethodizeClass方法中实现类中方法(协议等)的序列化。
- attachLists方法保证其添加到列表的前面
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
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]));
}
}
复制代码
- array()->lists: 类对象原来的方法列表,属性列表,协议列表,比如Person中的那些方法等
- addedLists:传入所有分类的方法列表,属性列表,协议列表,比如Person(Eat)、Person(Drink)中的那些方法等。
上面代码的作用就是通过memmove将原来的类找那个的方法、属性、协议列表分别进行后移,然后通过memcpy将传入的方法、属性、协议列表填充到开始的位置。
- 分类的方法、属性、协议只是添加到原有类上,并没有将原有类的方法、属性、协议进行完全替换
-
- 举个例子说明就是:假设原有类拥有 MethodA方法,分类也拥有 MethodA 方法,那> 么加载完分类之后,类的方法列表中会拥有两个 MethodA方法。
- 分类的方法、属性、协议会添加到原有类的方法列表、属性列表、协议列表的最前面,而原有类的方法列表、属性列表、协议列表则被移动到了列表的后面
-
- 因为运行时查找方法是顺着方法列表的顺序进行依次查找的,所以Category的方法会先被搜索到,然后直接指向,而原有类的方法则不被指向。这也是分类中的方法会覆盖掉原有类的方法最直接的原因。
分类的实现部分说起来很简单,不过源码确实有点恶心
load方法和initialize方法
load方法
我们知道,在类和category中都可以有+load方法,那么有两个问题:
1)、在类的+load方法调用的时候,我们可以调用category中声明的方法么?
2)、这么些个+load方法,调用顺序是咋样的呢?
并且在每个类中的load方法都添加了load方法
当在build Phases的Compile Sources中设置编译顺序如下时:
结果是
更换1和2的顺序
再当我们把主类放到这两个分类的编译顺序下面,是不是还会按照编译顺序运行?
可以看到并不是
带上子类呢?
对于load方法:
- 先调用类的load方法
-
- 按照编译先后顺序调用
-
- 调用子类的load之前会先调用父类的load
- 再调用分类的load
-
- 按照编译的先后顺序调用
对于“覆盖”的方法,会找到最后一个编译的方法,和我们上面理解的分类实现的方法一样
initialize
不带子类
initialize是在类的方法第一次被调用时执行
覆盖类中的方法 只执行一次
分类2的test在前面,先调用分类2,所以分类2的initialize方法先执行
带上子类
调用子类的分类的test方法,可以看到创建了两遍父类的initialize方法
为啥呢???
因为我在initialize方法中写了[super initialize],由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行
假如写了[super initialize]这时,系统就会再回到父类,再打印一遍。所以会打印两遍
关联对象
虽然在分类中可以写@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。
具体怎么实现,就涉及到关联对象了
就像这样
对于关联对象我们应该知道什么?
下次补充吧