category是Objective-C 2.0之后添加的语言特性.
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的加载流程
新建一个Person类,再写一个Person的分类Person+test实现父类的方法
Person *p = [Person new];
[p run];
//[Person(test) run]
最终输出分类的方法,分类的方法会覆盖原有类的方法吗?
- (void)printMethodOfClass:(Class)class{
unsigned int count ;
/** 获取方法数组 */
Method *methodList = class_copyMethodList(class, &count);
/** 存储方便名 */
NSMutableString *mothodNames = [NSMutableString string];
for (int i=0 ; i<count ; i++) {
/** 获取方法 */
Method method = methodList[i];
/** 获取方法名 */
NSString *methodName = NSStringFromSelector(method_getName(method));
/** 拼接方法名 */
[mothodNames appendString:methodName];
[mothodNames appendString:@", "];
}
free(methodList);
NSLog(@"%@ - %@", class, mothodNames);
}
Person - red(分类), red(原类), run(分类), run(原类),
通过对当前累的所有的方法的输出发现 ,并没有覆盖原类的方法
所有我们通过objc的源码分析一下分类的方法是如何加载的
- 首先来到objc_init中
void _objc_init(void)
{
//加载所有的image镜像文件的加载
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
load_images:app在编译完成之后producet里面就会有可执行文件。通过mach-O的分析会发现在Function Starts中已经通过map_images加载到map中
镜像文件.png
- _getObjc2CategoryList
2.1 remethodizeClass
2.2 attachCategories(Class cls, category_list *cats, bool flush_caches)
2.2.1 准备工作:method_list_t property_list_t protocol_list_t
2.2.2 操作
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);
2.2.3
接下来attachLists rw中,
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
然后原有的array开始改变,
- 数组扩容的过程:newCount = oldCount + addedCount数组数量的变化,紧接着realloc一下 ,原有的数组就变成了新的数组
- move©的操作
move:把分类的数组移动到原有数组的起点的位置就是lists[0]的地方
copy:对组装好的数组进行一步copy操作
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]));
所以在开始的时候[p run]的时候 输出分类的方法:原因是
首先会从自身的rw中开始查找,本身是一个list,第一个就是run,所以找到之后直接return IMP 了,就不会继续查询了 .
** 注意**
- 分类的方法是添加在本类的相对应的方法的前面并不是搜有的分类都在前面,因为复杂度的问题,因为一旦分类直接插入到最前面,methodList的所有的下标都要修改,但是插入到本类的星对应的方法面前,这样子本类方法的前面的methodList的下标是不需要动的。
- 分类的同名方法调用在本类之前
Category的编译顺序是有xcode ->Build Phases->Compile Sources 文件的顺序决定的
调用优先级:分类(category) > 本类 > 父类. 优先调用cateory中的方法,然后调用本类方法,最后调用父类方法。
详细分析可以看+load方法解析
面试点
• category不能添加成员变量,为什么
`struct objc_class {`
`Class isa OBJC_ISA_AVAILABILITY;`
`#if !__OBJC2__`
`Class super_class OBJC2_UNAVAILABLE; ``// 父类`
`const char *name OBJC2_UNAVAILABLE; ``// 类名`
`long version OBJC2_UNAVAILABLE; ``// 类的版本信息,默认为0`
`long info OBJC2_UNAVAILABLE; ``// 类信息,供运行期使用的一些位标识`
`long instance_size OBJC2_UNAVAILABLE; ``// 该类的实例变量大小`
`struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; ``// 该类的成员变量链表`
`struct objc_method_list **methodLists OBJC2_UNAVAILABLE; ``// 方法定义的链表`
`struct objc_cache *cache OBJC2_UNAVAILABLE; ``// 方法缓存`
`struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; ``// 协议链表`
`#endif`
`} OBJC2_UNAVAILABLE;`
ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。
• Category实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法的实现,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法.需要runtime动态添加绑定.
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self,@selector(name));
}
网友评论