什么是Category?
Category是Objective-C 2.0之后添加的语言特性,它的主要作用是为已经存在的类添加方法,一般称为分类。
Category在iOS开发中使用非常的频繁,特别是在为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最大程度的体现了Objective-C的动态语言特性。
Category结构体
新建了一个工程,创建了一个实体类,和一个category分类

cmd+B编译之后打开终端,cd到工程的目录,执行clang -rewrite-objc Test+categroy.m命令

Test+categroy.cpp文件

_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);
};
可以看出,在源码中Category的底层结构category_t和上文中我们得到的_category_t结构体基本一致,在编译之后,每个Category确实会生成一个category_t类型的结构体。而且category_t类型的结构体中是没有ivars这项,这就是分类是不能添加成员变量的原因。
Category加载流程
dyld加载
dyld是苹果的动态加载器,用来加载image(image指的是Mach-O格式的二进制文件,不是图片)- 当程序启动时,系统内核首先会加载
dyld,而dyld会将我们APP所依赖的各种库加载到内存中,其中就包括libobjc库(OC和runtime),这些工作,是在APP的main函数执行之前完成的 _objc_init是Object-C runtime的入口函数,在这里主要是读取Mach-O文件OC对应的Segment section,并根据其中的数据代码信息,完成为OC的内存布局,以及初始化runtime相关的数据结构。
_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);
}
- 调用一些init函数之后,重点在
_dyld_objc_notify_register这一句代码,这里注册了三个函数 &map_images将image加载进内存load_imagesdyld初始化加载image方法unmap_images移除内存 下面探究map_images这个函数
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
map_images_nolock函数是用来执行所有类注册和修复操作,并调用+load方法
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
...
//前面的代码省略,主要代码是_read_images这个函数
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
_read_images函数中有多个段落,我们研究的重点在于分类
// Discover categories.
for (EACH_HEADER) {
// 获取项目中所有的Category,得到一个二维数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 遍历二维数组,得到每一个category_t类型的结构体变量
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
// 通过cat->cls拿到当前Category所属的类
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;
}
// 判断class是否存在
bool classExists = NO;
// 如果Category中存在实例方法,协议或者是实例属性
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 将cls的未合并的所有Category存放到以cls为key的一个映射表中去
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 重新组织实例方法,将Category中的方法、属性、协议等等附加到cls的实例方法列表、实例属性列表和协议列表中去
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 如果Category中存在类方法,协议或者是类属性
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
// 重新组织元类方法,将Category中的类方法、类属性、协议等等附加到cls的元类的类方法列表、类属性列表和协议列表中去
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
- 进入
remethodizeClass函数,重点函数在attachCategories
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();
// 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];
// 取出分类中的方法存放到二维数组 mlists 中,isMeta决定是类方法还是实例方法
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();
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;
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]));
}
- 可以看出,分类的方法附加的类对象中的时候,只是把分类的方法放到原来的方法前面,这样runtime查找方法的时候就会先调用分类的方法