I、题:Category的使用场景是什么?
答:对于很复杂的类,可以使用Category把这个类拆分成很多个模块。Appdelegate这个类使用Category的地方比较多,有很多初始化的东西都可以放在分类里面实现,比如第三方配置(Umeng,极光推送,网络配置)。
结论:
- 目标类的分类中的实例方法、类方法都是存储在该目标类的类对象和元类对象中,并不会重新创建分类的类对象和元类对象。程序运行过程中会把分类中的实例方法全部合并到类对象中,把类方法全部合并到元类对象中;注意不是在程序编译的时候合并过去的,而是在程序运行的时候,通过Runtime动态将分类的方法合并到类对象和元类对象中。
- 目标类:即LQPerson+Test,LQPerson+Eat中的LQPerson。
- 当程序编译完的时候,创建的Category分类都会变成
category_t
这种结构体数据,方法,属性,协议等数据都会存储在该结构体里面。每一个分类都会生成一个category_t
结构体。
Category底层实现:
- 创建一个
LQPerson
的分类LQPerson+Test
; - 使用命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LQPerson+Test.m
编译得到cpp文件查看源码; - 编译后的源码
struct _category_t { const char *name; // 类名,如LQPerson 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; // 属性信息 };
- 最新
objc4-750
的Runtime源码中对category_t
的定义: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的加载处理过程:
- 通过Runtime加载某个类的所有Category数据;
- 把所有Category的方法、属性、协议数据合并到一个大数组中;(后参与编译的Category数据,会在数组的前面)
- 将合并的分类数据(方法、属性、协议),插入到目标类原来的数据前面。
第三步的核心操作:
// cls = [LQPerson class];
// cats = [category_t(Test), category_t(Eat)];
// 下面注释说明使用的是类对象,元类对象同理。
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_t, method_t], // 一个分类的方法列表
[method_t, method_t] // 另一个分类的方法列表
]
*/
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
// 属性数组
/*
[
[property_t, property_t], // 一个分类的属性列表
[property_t, property_t] // 另一个分类的属性列表
]
*/
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
// 协议数组
/*
[
[protocol_t, protocol_t], // 一个分类的协议列表
[protocol_t, protocol_t] // 另一个分类的协议列表
]
*/
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--) {
// 取出某个分类,entry.cat就是`category_t`类型数据
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);
}
/*addedLists:
[
[method_t, method_t], // 一个分类的方法列表
[method_t, method_t] // 另一个分类的方法列表
]
addedCount:2
*/
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]));
}
}
源码解读顺序:
- objc-os.mm
_objc_init_
map_images
map_images_nolock
- objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
-
realloc
、memmove
、memcpy
注意:images不是图片的意思,是镜像、模块的意思。
J、题:Category的实现原理是什么?
答:Category编译之后的底层结构是
struct category_t
,里面存储着Category的对象方法、类方法、属性、协议信息;在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象,元类对象)
K、题:分类(Category)和类扩展(Class Extension)有什么区别?
答:类扩展在编译的时候他的数据就已经包含在类信息中,而分类里面的内容是在程序编译的时候生成
category_t
结构体数据,在运行的时候利用运行时机制合并到类信息里面去的。
memmove、memcpy区别
memcpy在移动数据的时候容易出现错误
memmove在移动数据的时候可以保证数据的完整
L、题:把3412中的34移动到41位置上面,分别用上面2个命令会出现什么问题?
答:
使用memcpy
:会分别把3和4copy到4和1的位置,但是会有先后顺序,会先把3copy到4的位置,然后再把4copy到1的位置(步骤A),但是当把3copy到4的位置的时候,4的位置的值就不是4了,就变成3了,再来执行步骤A的时候就会把3copy到1的位置了,所以结果就变成了3332了。
使用memmove
:会先判断是向前移动还是向后移动,如果是向后移动,就会先将4移动到1的位置,原来的位置还是4,此时是3442,然后再将3移动到4的位置,结果就变成了3342。
网友评论