什么是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;
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,
(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,
};
对象方法列表结构体
【对象方法的实现】
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 {
unsigned int entsize;
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 {
unsigned int entsize;
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;
const char *protocol_name;
const struct _protocol_list_t * protocol_list;
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;
const unsigned int flags;
const char ** extendedMethodTypes;
};
【分类中添加的协议列表结构体】
static struct {
long protocol_count;
struct _protocol_t *super_protocols[1];
} _OBJC_PROTOCOL_REFS_TestProtocol __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
【协议列表 对象方法列表结构体】
static struct {
unsigned int entsize;
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 {
unsigned int entsize;
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 {
unsigned int entsize;
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
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
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
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();
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;
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();
if ((cats = unattachedCategoriesForClass(cls, false))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true );
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));
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
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);
}
- 先创建方法列表、属性列表、协议列表的新列表并且给它们分配内存,然后存储该cls所有的分类的方法、属性、协议,然后转交给了attachMethodLists方法(就是后面的那些写在一起了)
为什么需要准备方法列表这一步呢?
方法的查找算法是通过二分查找算法,说明sel-imp是有排序的,那么是如何排序的呢?
perpareMethodLists中主要调用了fixup方法 在 fixupMethodList 方法中会遍历 mlist,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist 进行重新排序。
这也就意味着 remethodizeClass方法中实现类中方法(协议等)的序列化。
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
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) {
list = addedLists[0];
}
else {
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方法:
对于“覆盖”的方法,会找到最后一个编译的方法,和我们上面理解的分类实现的方法一样
initialize
不带子类
initialize是在类的方法第一次被调用时执行 覆盖类中的方法 只执行一次 分类2的test在前面,先调用分类2,所以分类2的initialize方法先执行 
带上子类  调用子类的分类的test方法,可以看到创建了两遍父类的initialize方法 为啥呢??? 
因为我在initialize方法中写了[super initialize],由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行 
假如写了[super initialize]这时,系统就会再回到父类,再打印一遍。所以会打印两遍 
关联对象
虽然在分类中可以写@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。
具体怎么实现,就涉及到关联对象了  就像这样
对于关联对象我们应该知道什么? 下次补充吧
|