Category在iOS开发中使用很是的频繁,特别是在为系统类进行拓展的时候,咱们能够不用继承系统类,直接给系统类添加方法,最大程度的体现了Objective-C的动态语言特性。安全
本文篇幅较长,但内容完整,建议能跟随文章内容探索一遍,毕竟实践出真知。bash
将类的实现分散到多个不一样的文件或多个不一样的框架中。以下:不一样的功能模块用不一样的Category处理 数据结构
能够在不修改原来类的基础上,为一个类添加扩展方法。如咱们须要给系统自带的类添加方法。框架
会覆盖原类中方法名相同的方法,多个Category的同名方法,会按照编译顺序,执行最后编译
的Category里的方法。 ide
和Extension(扩展)
的区别:函数
@property
添加属性,可是Category添加的属性不能生成成员变量和getter
,setter
方法的实现,即不能经过_var
调用,不过能够手动经过objc_get/setAssociatedObject
手动实现。上面说了一大堆,如今正式开始探索历程,探索上面解释的正确性,先来看看编译期干了什么吧。工具
新建了一个测试工程,建立了一个实体类,和一个Test分类 , 分类里声明一个Test方法,并cmd+B
编译一下 布局
打开命令行工具,cd到当前目录下,执行clang -rewrite-objc Person+Test.m
命令,而后找到当前工程文件夹,找到编译后的 Person+Test.cpp
文件并打开测试
文件内容很是多,因为知道category
的结构体是_categoy_t
, 全局搜索找到了它的结构体ui
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_t
,找到了咱们的测试的分类static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
0,
0,
0,
};
复制代码
section ("__DATA,__objc_const")
,是一个段标识,它会存放到咱们的dyld
加载Macho
可执行文件里的这个section
段里(关于dyld及Macho文件内容较多,这里暂时不作详细解释,这里了解到它会在编译的时候会存放到Macho
文件里便可)_category_t
的结构体,发现这里的类名是Person
,实例方法列表里存放了一个Test
方法_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"Test", "v16@0:8", (void *)_I_Person_Test_Test}}
};
复制代码
_objc_method
结构体对照着能够看到imp
里包含了Sel
方法编号,方法签名及真实的函数地址struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
复制代码
category
编译成结构体,而后把对应的值填充到结构体里,保存在Macho
可执行文件里_category_t
,发现还有以下代码static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Person_$_Test,
};
复制代码
category
方法都保存到__objc_catlist
里,也就是在加载到Macho
文件里的对应的section
段里在编译期,把category
编译成对应的结构体保存到Macho
文件里的section
段,把全部的分类方法都保存到Macho
文件__objc_catlist
这个section
段里
了解到编译器主要作了保存的操做,那么运行期毫无疑问是作的加载操做,须要把刚刚编译期保存的内容都进行加载。
先来看看Category
是如何被加载的
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
是不是入口函数,打开刚才的测试工程,添加一个符号断点_objc_init
,而后运行工程
_objc_init
,接下来才会执行
dyld_start
加载函数
源码工程objc4网盘连接 密码:tuw8
objc4
,直接搜索_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_images
dyld初始化加载image方法unmap_images
移除内存咱们要探索Category
是如何被加载进内存的,因此要看&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);
}
复制代码
_read_images
void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) {
//其他无关代码已省略
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
复制代码
_read_images
函数里的内容较多,找到与category
相关的代码,这段代码较长void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//其他代码已省略
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);
}
}
}
}
复制代码
_getObjc2CategoryList
函数是读取全部的category
方法,点进该方法能够看到它其实就是读取咱们编译期时的_objc_catlist
的section
段内容GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist");
复制代码
if (cat->instanceMethods)
能够看到这里判断了当前category
的方法是类方法仍是实例方法,并分别作不一样的处理addUnattachedCategoryForClass(cat, cls, hi);
这里是把category
与该class
原类关联映射起来,能够点进去该方法看内容
remethodizeClass(cls);
,看名字像是从新设置类里面的函数,点进去看看具体函数内容static void remethodizeClass(Class cls) {
//多余代码省略
category_list *cats;
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
复制代码
attachCategories
关联分类函数的具体实现static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
//省略无关代码
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));
//省略无关代码
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
}
复制代码
mlists
prepareMethodLists
和attachLists
prepareMethodLists
//省略其余无关代码
for (int i = 0; i < addedCount; i++) {
//把要添加原类的方法列表取出来
method_list_t *mlist = addedLists[i];
assert(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
复制代码
取出来方法列表后,调用了fixupMethodList
,点进去看看
这里作的是把方法列表里的方法都注册到原类里
总之,prepareMethodLists
作的是添加方法列表前的准备工做
回到外面,点击进入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;
/** 拷贝的操做 void *memmove(void *__dst, const void *__src, size_t __len); */
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
复制代码
memmove
和memcpy
函数完成了拷贝操做,那么这两个函数具体究竟是作了什么呢?
memmove
是把原类里的方法列表,向后移动了要添加的方法列表的大小的距离![]()
memcopy
是把要添加的方法列表拷贝到原类刚刚的方法列表里空出来的位置上![]()
众所周知,在分类里能够经过objc_get/setAssociatedObject
来模拟添加属性,那么它究竟是如何实现的呢?
objc_setAssociatedObject
,找到其方法内容void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
复制代码
继续找_object_set_associative_reference
,这里的代码较多,逐行分析
acquireValue
是进行内存管理,能够点进去看一下
这里有一个AssociationsManager
,看到它里面有个AssociationsHashMap
,而且访问会加锁,是线程安全的
disguised_ptr_t disguised_object = DISGUISE(object);
这里使用对象取反后的值做为key,迭代器里的value 是ObjectAssociationMap
再看ObjectAssociationMap
,它的key是用户传进来的自定义key
,它的value是ObjcAssociation
还有最后一个重要的方法setHasAssociatedObjects
,这里把属性和类关联起来,而且设置isa指针的标识isa.has_assoc
,以便释放的时候使用
同理,objc_getAssociatedObject
也是从这里取出来值的
在上面咱们知道属性是经过类的isa关联起来的,那么理应在这个对象销毁的时候一块儿移除该属性。 一样的在当前objc源码里搜索dealloc
,找到了它的实现
- (void)dealloc {
_objc_rootDealloc(self);
}
复制代码
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
复制代码
rootDealloc
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
复制代码
object_dispose
id object_dispose(id obj) {
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
复制代码
objc_destructInstance
里找到了属性的销毁void *objc_destructInstance(id obj) {
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
复制代码
这里有经过isa.has_assoc
标识符判断当前对象是否有关联属性,若是有就调用上面代码里的_object_remove_assocations
移除关联属性
继续看_object_remove_assocations
,和咱们设置关联属性的代码相似,不过这里是取出来而后delete refs
删除
以上就是探索Category
底层原理的整个过程,也使得文章开头的Category
的做用获得验证。整个过程是枯燥和冗长的,可是探索完仍是有很大的收获。本文篇幅很长,但愿你们也能亲自试着探索一遍,能不只仅知足于知道这是什么,还要去探究为何会是这样。