作用:
当为类添加新方法时,OC一共提供了4中方式:
- 直接在类中再写一个方法,缺点:破坏了类的封装性。
- 通过继承再子类里扩展一个方法,缺点:开销大、增加代码复杂度
- 通过协议实现扩展一个类的方法,优点:该方式能够大大降低代码的耦合度,但是实现上会更复杂。
- 最后一种就是使用Category。
优点和缺点:
Category优点:
- 在不改变一个类的情况下,对一个已存在的类添加新的方法
- 可以在没有源代码的情况下对框架内的类进行扩展,例如NSString。
- 减小个文件的体积
- 可以按需加载不同的Category
Category缺点:
- 方法的扩展是硬编码,不能动态添加
- Category中方法的优先级高于原类中的方法,所以Category中的方法可能会覆盖原有类中相同名字的方法,从而造成未知问题。
- 不能添加成员变量(并非不能,只不过比较复杂)
- 可以添加属性,但是不会自动生成getter和setter方法,需要通过关联对象实现。
- 同一个类的Category中不能有重复名字的方法。
Category实现的原理:
Category源码在:objc-runtime-new.h和objc-runtime-new.mm这两个文件中
Category runtime中的定义:
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);
};
通过:
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
结构体中这四个变量可以看出,分类可以添加实例方法、类方法、协议和属性列表,但是唯独不能添加实例变量,因为没有存储实例变量对应的指针变量。
Category加载的详细步骤和调用到的函数:
_objc_init(void)->map_images(...)->map_images_nolock(...)->_read_images(...)->remethodizeClass(Class cls)->attachCategories(Class cls, category_list *cats, bool flush_caches)->void attachLists(List* const * addedLists, uint32_t addedCount
函数介绍:
- _objc_init:是runtime的入口函数,进行一些初始化操作
- map_images:加锁
- map_images_nolock:完成所有类的注册和fixup等工作,还包括一些初始化工作以及调用load类方法。
- _read_images:完成类的加载、协议的加载、类别的加载等工作
- remethodizeClass:将类别绑定到目标类上
- attachCategories:将类别中的方法和属性绑定到目标类上。
- attachLists:将目标类中的方法和分类中的方法放到一个列表中。
主要涉及Category原理的三个方法:_read_images、attachCategories、attachLists
来看看_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) {
// 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);
}
}
}
}
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()
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
if (hi->isPreoptimized()) {
_objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
"in %s", hi->fname());
}
else if (hi->info()->optimizedByDyld()) {
_objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
"in %s", hi->fname());
}
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->isPreoptimized()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
_objc_inform("PREOPTIMIZATION: %zu selector references not "
"pre-optimized", UnfixedSelectors);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
PreoptOptimizedMethodLists, PreoptTotalMethodLists,
PreoptTotalMethodLists
? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
: 0.0);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
PreoptOptimizedClasses, PreoptTotalClasses,
PreoptTotalClasses
? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
: 0.0);
_objc_inform("PREOPTIMIZATION: %zu protocol references not "
"pre-optimized", UnfixedProtocolReferences);
}
其中在代码中:
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" : "");
}
}
在有注释的地方,就是重要关键点:1、去绑定分类和目标类。2、重新构建方法列表。
接下来我们来看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];
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);
}
从源码可以看出,这里是将目标类中所有扩展出来的类别中的属性、方法、协议列表头取出放到一个列表里面,然后统一交给attachLists这个函数去处理。
接下来在看看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]));
}
}
这里强调一下,oldCount和newCount分别代表的是目标类中方法、属性、协议列表的长度还有类别中的方法、属性、协议列表的长度。通过realloc函数申请内存,这里使用realloc申请内存有几种情况,大家可以自行查阅,这里不再描述,当realloc重新申请好内存后,使用memmove和memcpy这两个函数,将列表内容放到我们重新申请的内存中,从这里可以得到,Category中的方法并没有覆盖目标类中的方法,只不过将Category中的方法放到了目标类方法的前面而已,在调用方法的时候,若Category中和目标类中有同名的方法,系统会先找到放到前面的类别中的方法,这就是为什么Category中方法的优先级高于目标类中的方法。
Category添加属性:
尽管在源码中我们看到了指向属性列表的指针变量,但是系统并没有给这里的属性生成getter和setter函数,需要手段生成实例变量,并为其生成getter和setter方法。
网友评论