分类(category):可以在不修改原来类的基础上,为一个类扩展方法。该方法可以不写实现(.m)。category是在程序加载运行的时候,才加载到内存中,此时类已经创建完成,内存已经申请了,因此不能再更改。但是每个对象结构体都存储着相对应的hashmap来表达类的名字、方法、属性等等。本文介绍一下利用runtime给该哈希表添加属性、方法,以及多个分类的加载和调用顺序。
扩展 (extension):category的一个特例,有时候也被称为匿名分类。他的作用是为一个类添加一些私有的成员变量和方法。多用于.m
最简单最常见的扩展,.m中文件头部的扩展:
@interface ViewController ()
@end
创建分类:
我这里是创建了两个分类,一个用于测试公有,一个用于测试私有.
PS:有时候会根据不同业务去创建不同的分类。或者类过大也会创建多个分类。
说下多个分类的问题:
1.分类与分类之间的方法和属性不能互相调用,但是都可以调用基类。
2.分类中原则上只能添加私有属性,下面会讲到通过runtime添加公有属性。而且setter和getter方法需要手动实现。
3.分类中方法名字重复的话会优先调用私有的。在本类和分类有相同的方法时,直接覆盖基类方法。
如果有两个分类,他们都实现了相同的方法,如何判断谁先执行?分类执行顺序可以通过targets,Build Phases,Complie Source进行调节,注意执行顺序是从上到下的。(只有两个相同方法名的分类)
category 方法可能会覆盖于同一个类class 的其它 category 中的方法。但也可能被覆盖,因为不法预知他们的加载优先顺序,出现这种情况通常会在编译时出错。如果在一个开发的SDK中使用了类别, 就最好保证类别名不同于使用者的类别名,以及类别方法也不同于使用者的类别方法名, 通常通过加前缀来做到。
注意:这里经过测试,分类的调用跟创建(添加进项目)时间有关,跟调用顺序无关。如果多个分类方法名冲突,会调用最后创建的那一个,前面会被覆盖。(更正一下,创建不是指创建时间,而是指加入项目的时间,即便你去年就创建了分类A,现在创建了分类B。然后先加入B,后加入A。调用的方法还是优先调用A)
另外一点,试验证明,你创建了分类,即便头文件不引入,只要方法名相同,还是会调用后来创建的。因为这代表调用了私有方法。
(这个结论是亲自测试的,网上搜半天并没有得到什么有用的答案。还是自己亲自来靠谱)
4.分类的方法优先级高,但是不可以调用super方法。
利用category添加属性。
这里分别是两种不同类型的属性,基本类型和对象类型。
/*
* id object 给哪个对象的属性赋值
const void *key 属性对应的key
id value 设置属性值为value
objc_AssociationPolicy policy 使用的策略,是一个枚举值,和copy,retain,assign是一样的,IOS开发一般都选择NONATOMIC
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
*/
源对象,关联时的用来标记是哪一个属性的key(因为你可能要添加很多属性),关联的对象和一个关联策略。
用来标记是哪一个属性的key常见有三种写法:
static NSNumber *numKey;
static void *numKey = &numKey;
static char numKey;
更新:
分类的命名要尽量带前缀,方便区分。
runtime 严谨写法:
- (NSString*)YYURLString{
return objc_getAssociatedObject(self, ((__bridge void*)PageNavigationURLStringKey));
}
- (void)setYYURLString:(NSString*)urlString{
objc_setAssociatedObject(self, ((__bridge void*)PageNavigationURLStringKey),urlString, OBJC_ASSOCIATION_RETAIN);
}
主要在于前面加了个__bridge void * 实现id类型与void*类型的相互转换。方便通用OC和C指针,这点在MRC转ARC的时候用的比较多。ARC下的void和id不互通造成的。
拓展:这种利用Runtime赋值的方式,可以将某个对象赋值给某个类,相当于页面间传值。