iOS-category
2019-01-11 本文已影响0人
李永开
一.简介
category可以不通过继承的方式给类添加额外的方法.
@interface NSString (catas)
- (NSString *)retInstanceMethod;
+ (NSString *)retClassMethod;
- (BOOL)isEqualToString:(NSString *)aString;
@end
@implementation NSString (catas)
- (NSString *)retInstanceMethod
{
return @"实例方法哦";
}
+ (NSString *)retClassMethod
{
return @"类方法哦";
}
- (BOOL)isEqualToString:(NSString *)aString
{
return YES;
}
# 给NSString添加了一个 retInstanceMethod()实例方法,所有继承NSString的对象都可以使用retInstanceMethod().
NSString *str = @"hello wordl";
[str retInstanceMethod];
# 给NSString添加了一个 retClassMethod()类方法,所有继承NSString的对象都可以使用retClassMethod().
[NSString retClassMethod];
二.使用runtime分析category
1.分类的实例方法
#打印NSString方法列表 unsigned int count; Method *methodArr = class_copyMethodList(NSString.class, &count); for (int i =0; i < count; i ++) { Method method = methodArr[i]; SEL sel = method_getName(method); NSLog(@"NSString的实例方法[%d]:%s",i,sel_getName(sel)); } //释放 free(methodArr); 打印:共589个方法,筛选retInstanceMethod得到如下列表 2019-01-11 17:18 iOSWorld[884:21371] NSString的实例方法 [3]:retInstanceMethod
我们发现NSString (catas)这个分类的方法被添加到了NSString这个类的方法列表中.
2.分类的类方法
//打印NSString元类(meta_Class)的类方法列表 unsigned int count; Method *methodArr = class_copyMethodList(object_getClass(NSString.class), &count); for (int i =0; i < count; i ++) { Method method = methodArr[i]; SEL sel = method_getName(method); NSLog(@"NSString的类方法[%d]:%s",i,sel_getName(sel)); } //释放 free(methodArr); 打印:共94个类方法,筛选retClassMethod 2019-01-11 iOSWorld[910:23001] NSString的元类的类方法方法 [5]:retClassMethod
发现NSString (catas)的类方法被添加到了NSString元类的类方法列表中.
- 注意
NSString.class
和object_getClass(NSString.class)
的区别
3.分类中重写已有的方法
unsigned int count; Method *methodArr = class_copyMethodList(NSString.class, &count); for (int i =0; i < count; i ++) { Method method = methodArr[i]; SEL sel = method_getName(method); NSLog(@"NSString的实例方法[%d]:%s",i,sel_getName(sel)); } //释放 free(methodArr); 打印:发现有两个一模一样的isEqualToString:方法 2019-01-11 17:46:53 iOSWorld[935:25910] NSString的实例方法[47]:isEqualToString: 2019-01-11 17:46:53 iOSWorld[935:25910] NSString的实例方法[513]:isEqualToString:
所以说:分类中重写原来类的方法并不是replace原来的方法,而是添加了一个方法,而且新添加的方法调用顺序在原方法之上
看起来好像被替换了 0.0
- 如果两个分类都重写了原来的方法,那么方法调用优先级要看Build phases - Compile Sources的编译顺序,谁后被添加谁的优先级就更高
三.总结
category本质:我们编写的category在编译时会生成一个struct catagory_t,里面包含了category的名称、方法列表等,然后使用runtime将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加载过程
- objc-os.mm文件加载
_objc_init()
- _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);
}
- 加载镜像
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
- 先加载
&map_images
--->load_images就是加载load方法
return map_images_nolock(count, paths, mhdrs);
- 读取镜像
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
- 初始化类表,将方法添加到方法列表中,添加分类的方法
也就是说:编译的时候只编译变量,无关方法列表,方法类表是在运行时添加的
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses{
//1.如果是第一次,则初始化类名表
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
//2.把类添加到类表中去,并将标记懒加载的类,标记了懒加载类方便后续的初始化它们
gdb_objc_realized_classes
//3.重映射类,重映射的类都是非懒加载的类
// 4.把方法添加到方法列表中去
//---sel_cname函数内部就是将SEL强转为常量字符串
const char *name = sel_cname(sels[i]);
//---把方法名添加到namedSelectors表中去
sels[i] = sel_registerNameNoLock(name, isBundle);
//6.把协议添加到protocol_map表中去
//7.重新映射协议表,因为优化后的images可能是正确的,但是并不确定
过程:查看表中的value:protocol_t能否对的着
//8.初始化所有非懒加载类的一些信息(rw、ro)
//9.初始化所有懒加载类的一些信息(rw、ro)
//10.发现分类
//如果是实例方法
if (cat->instanceMethods || cat->protocols || cat->instanceProperties)
//---将未添加到类的分类添加分类表中去
addUnattachedCategoryForClass(cat, cls, hi);
//---将分类的method、protocol、property添加到class中去
remethodizeClass(cls);
//如果是类方法
if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties))
//将分类添加到元类的分类数组中去
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
}
接下来是remethodizeClass
添加过程的实现
//---获取类对应的分类数组,并从分类的哈希表中删除掉分类数组
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/)))
{
if (PrintConnecting)
{
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
//---添加分类的方法、协议、属性到class中去
attachCategories(cls, cats, true /*flush caches*/);
}
//开始添加---将类和类别的方法传进去
category_list *cats;
attachCategories(cls, cats, true /*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));
//遍历所有分类,把每个分类里的方法添加到临时数组中去
//这里是倒序添加分类方法的,后编译的分类方法在前面
while (i--)
{
//---返回类的方法列表,并拼接在临时方法数组中
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//---返回类的属性列表,并拼接在临时属性数组中
//---返回类的协议列表,并拼接在临时协议数组中
auto rw = cls->data();
//1.把分类中的方法添加class中的方法列表里去
rw->methods.attachLists(mlists, mcount);
//2.把分类中的属性添加class中的属性列表里去
rw->properties.attachLists(proplists, propcount);
//3.把分类中的协议添加class中的协议列表里去
rw->protocols.attachLists(protolists, protocount);
//attachLists的实现
//旧方法列表往后移动addedCount个位置
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//新方法列表拷贝到array()->lists表头到addedCount-1处
memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
}
五.骚操作
- 我使用分类重写了原来类的方法,但我还想使用原来那个方法怎么办?
答案:你可以使用runtime的class_copyMethodList去遍历方法,并调用. - 怎样给category增加属性?
答案: 可以使用runtime的AssociatedObject(关联对象
)来实现.
拓展 : associatedObject实现原理
- 系统会生成一个associationsManager.
- associationsManager管理着一个associationsHashMap
- associationsHashMap的key为实例对象,value为另一个associationMap
- associationMap的key为我们传进去的key(&indexKey),value为我们传进去的value和OBJC_ASSOCIATION_ASSIGN
objc_setAssociatedObject(self, &indexKey, value, OBJC_ASSOCIATION_ASSIGN);
//manager里面有个AssociationsHashMap
class AssociationsManager {
static AssociationsHashMap *_map;
}
//
注意事项:
1.
objc_setAssociatedObject()
需要传一个const void * _Nonnull key
类型的key,也就是一个指针
2.定义一个static const void *key = &key
,将自己的地址赋给key,可以直接将key传进去.加static
是为了不让其他类拿到key这个变量.
3.或者:static const char key
,将&key传进去.
4.或者:直接写字符串. key = @"name";以为字符串都是放在常量区的,永不变.
5.或者:写@selector(name),因为@selector(name)在一个类中也是独有的.