美文网首页
iOS 分类 category(二)源码实现

iOS 分类 category(二)源码实现

作者: 萨缪 | 来源:发表于2020-04-27 22:35 被阅读0次

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_CATEGORY_INSTANCE_METHODS_CategoryObject__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_PROP_LIST_CategoryObject__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_CATEGORY_CategoryObject__MyAddition attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS
_CategoryObject, (const struct _method_list_t *)&_OBJC_CATEGORY_INSTANCE_METHODS_CategoryObject_MyAddition, 0, 0, (const struct _prop_list_t *)&_OBJC_PROP_LIST_CategoryObject_MyAddition, }; static void OBJC_CATEGORY_SETUP_CategoryObject_MyAddition(void ) { _OBJC_CATEGORY_CategoryObject_MyAddition.cls = &OBJC_CLASS__CategoryObject;
}

static struct /_method_list_t/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[3];
} OBJC_CATEGORY_INSTANCE_METHODS_CategoryObject__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_PROP_LIST_CategoryObject__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_CATEGORY_CategoryObject__MyAddition2 attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS
_CategoryObject, (const struct _method_list_t *)&_OBJC_CATEGORY_INSTANCE_METHODS_CategoryObject_MyAddition2, 0, 0, (const struct _prop_list_t *)&_OBJC_PROP_LIST_CategoryObject_MyAddition2, }; static void OBJC_CATEGORY_SETUP_CategoryObject_MyAddition2(void ) { _OBJC_CATEGORY_CategoryObject_MyAddition2.cls = &OBJC_CLASS__CategoryObject;
}

pragma section(".objc_inithooks$B", long, read, write)

__declspec(allocate(".objc_inithooksB")) static void *OBJC_CATEGORY_SETUP[] = { (void *)&OBJC_CATEGORY_SETUP_CategoryObject_MyAddition, (void *)&OBJC_CATEGORY_SETUP_CategoryObject_MyAddition2, }; static struct _class_t *L_OBJC_LABEL_CLASS_ [1] attribute((used, section ("__DATA, _objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS
_CategoryObject, }; static struct _category_t *L_OBJC_LABEL_CATEGORY_ [2] attribute((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&OBJC_CATEGORY_CategoryObject__MyAddition,
&OBJC_CATEGORY_CategoryObject__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_CATEGORY_INSTANCE_METHODS_CategoryObject__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_PROP_LIST_CategoryObject__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_CATEGORY_CategoryObject__MyAddition attribute ((used, section ("__DATA,_objc_const"))) =
{
"CategoryObject",
0, // &OBJC_CLASS
_CategoryObject, (const struct _method_list_t *)&_OBJC_CATEGORY_INSTANCE_METHODS_CategoryObject_MyAddition, 0, 0, (const struct _prop_list_t *)&_OBJC_PROP_LIST_CategoryObject$_MyAddition,
};

这里就是给_category_t 结构体赋值,结构体名字规则是 文件头 + 类名+ 类别名。不过这里的 classMethods 和 protocols 都是0 ,因为我们没有给类别增加这些东西。所以都是0.

static void OBJC_CATEGORY_SETUP__CategoryObject__MyAddition(void ) {
OBJC_CATEGORY_CategoryObject_MyAddition.cls = &OBJC_CLASS$_CategoryObject;
}

我们看category的 cls变量没有赋值。这里给出一个单独的函数对cls进行赋值。

static struct category_t *L_OBJC_LABEL_CATEGORY[2] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { &_OBJC_CATEGORY_CategoryObject_MyAddition, &_OBJC_CATEGORY_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调用,所以可以倒叙遍历方法列表,只遍历第一个方法即可,这个方法就是原类的方法。

分类源码

相关文章

网友评论

      本文标题:iOS 分类 category(二)源码实现

      本文链接:https://www.haomeiwen.com/subject/sappwhtx.html