4.runtime的使用
1. 一些类的含义:
- SEL:选择子,用于在运行时表示 方法的名称 ,是已经在Objective-C运行时注册(或“映射”)的C字符串。
在使用选择器时,必须使用从sel_registerName或Objective-C编译器指令@selector()返回的值。您不能简单地将C字符串转换为SEL。
typedef struct objc_selector *SEL;
-
IMP:指向 方法实现 开始的指针
函数有2个参数,第一个是指向self的指针,第二个是方法的选择子。 -
Method:方法,表示类定义范畴下方法的定义。
typedef struct objc_method *Method;
- NSMethodSignature :方法的返回值和参数的类型信息的记录
使用NSObject methodSignatureForSelector: 来获取methodSignature。
- NSInvocation :作为对象呈现的Objective-C消息。
An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value.
包含消息的所有要输。
2. runtime的使用
- 实现多继承,Multiple Inheritance
- Method Swizzling
- Aspect Oriented Programming
- isa Swizzling
- Associated Object 关联对象
- 动态的增加方法
- NSCoding的自动归档和解档
8.字典模型相互转换
一: 实现多继承 Multiple Inheritance
OC程序中可以借用消息转发机制来实现多继承的功能。
用 forwardingTargetForSelector: 和 forwardInvocation:方法。上图中,
在图中,warrior实例转发了一个negotiate消息到Diplomat实例中,执行Diplomat中的negotiate方法,结果看起来像是warrior实例执行了一个和Diplomat实例一样的negotiate方法,其实执行者还是Diplomat实例。
这使得不同继承体系分支下的两个类可以“继承”对方的方法,这样一个类可以响应自己继承分支里面的方法,同时也能响应其他不相干类发过来的消息。在上图中Warrior和Diplomat没有继承关系,但是Warrior将negotiate消息转发给了Diplomat后,就好似Diplomat是Warrior的超类一样。
消息转发和多继承的不同:
多继承:合并了不同的行为特征在一个单独的对象中,会得到一个重量级多层面的对象。
消息转发:将各个功能分散到不同的对象中,得到的一些轻量级的对象,这些对象通过消息转发联合起来。
即使我们利用转发消息来实现了“假”继承,但是NSObject类还是会将两者区分开。像respondsToSelector:和 isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链。
二: Method Swizzling
1.method swizzling 原理
Method Swizzling是发生在运行时的,主要用于在运行时将两个Method进行交换,Method Swizzling本质上就是对IMP和SEL进行交换。
2. method Swizzling的使用代码
代码模板如下
#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
// 1.在load 方法中实现。
+ (void)load {
//2. 使用dispatch_once;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// 3. 获取SEL
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
// 4. 获取Method
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 5. 交换方法实现。
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
3. method Swizzling 注意点
-
主要是用在分类中,交换类中的某个方法的实现, 在保证原来方法实现的基础上,添加一些自己的功能。而不用继承的方式。
-
Objective-C在运行时会自动调用类的两个方法+load和+initialize。+load会在类初始加载时调用, +initialize方法是以懒加载的方式被调用的
3.Swizzling应该总是在dispatch_once中执行
Swizzling会改变全局状态,所以在运行时采取一些预防措施,使用dispatch_once就能够确保代码不管有多少线程都只被执行一次。这将成为Method Swizzling的最佳实践。
4.method Swizzling 的使用场景。
-
实现aop
-
实现埋点统计。
-
实现异常保护,比如hook NSArray的objectAtIndex方法,对越界做处理。
三。Aspect Oriented Programming
AOP定义: 面向切片编程
类似记录日志、身份验证、缓存等事务非常琐碎,与业务逻辑无关,很多地方都有,又很难抽象出一个模块,这种程序设计问题,业界给它们起了一个名字叫横向关注点(Cross-cutting concern),AOP作用就是分离横向关注点(Cross-cutting concern)来提高模块复用性,它可以在既有的代码添加一些额外的行为(记录日志、身份验证、缓存)而无需修改代码。
我们使用消息转发机制 和 method Swizzling 来达到aop的目的。
四: Isa Swizzling
黑魔法Method Swizzling,本质上就是对IMP和SEL进行交换。Isa Swizzling本质上也是交换,不过交换的是Isa。
苹果使用的 Key-Value Observing 即KVO也用到了Isa Swizzling。
KVO是为了监听一个对象的某个属性值是否发生变化。在属性值发生变化的时候,肯定会调用其setter方法。所以KVO的本质就是监听对象有没有调用被监听属性对应的setter方法。具体实现应该是重写其setter方法即可。
KVO在调用addObserver方法之后,苹果的做法是在执行完addObserver: forKeyPath: options: context: 方法之后,把isa指向到另外一个类去。NSKVONotifying_XXX。
在这个新类里面重写被观察的对象四个方法。class,setter,dealloc,_isKVOA。
1. 重写class方法:
重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容。
2.重写setter方法:
在新的类中会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
在didChangeValueForKey:方法再调用:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
注意: 如果使用了点语法,会在setter中调用will/didChangeValueForKey:方法。
如果没有使用点方法,而使用KVC,运行时会在setValue:forKey方法中调用will/didChangeValueForKey:方法。 也可以触发KVO。
直接调用will/didChangeValueForKey:方法。也可以触发KVO。
3. 重写dealloc方法
销毁新生成的NSKVONotifying_类
4.重写_isKVOA方法
这个私有方法估计可能是用来标示该类是一个 KVO 机制声称的类。
5.KVO的缺陷:
主要问题在KVO的回调机制,不能传一个selector或者block作为回调,而必须重写-addObserver:forKeyPath:options:context:方法所引发的一系列问题。而且只监听一两个属性值还好,如果监听的属性多了, 或者监听了多个对象的属性, 那有点麻烦,需要在方法里面写很多的if-else的判断。
五:Associated Object关联对象
通过关联对象来实现在 Category 中添加属性的功能了。
1.用法
// NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
// NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
参数的含义:
-
id object 设置关联对象的实例对象
-
const void *key 区分不同的关联对象的 key。
3.id value 关联的对象
4.objc_AssociationPolicy policy 关联对象的存储策略,它是一个枚举,与property的attribute 相对应。
注意1:标记成OBJC_ASSOCIATION_ASSIGN的关联对象和
@property (weak) 是不一样的,上面表格中等价定义写的是 @property (unsafe_unretained),对象被销毁时,属性值仍然还在。
注意2: objc_removeAssociatedObjects。这个方法是移除源对象中所有的关联对象,并不是其中之一。所以其方法参数中也没有传入指定的key。要删除指定的关联对象,使用 objc_setAssociatedObject 方法将对应的 key 设置成 nil 即可。
2.关联对象的原理
使用AssociationsManager 来管理2张哈希表:
第一张AssociationsHashMap: key为object的地址的指针,value为一张ObjcAssociationMap。
第二张ObjcAssociationMap表:key值是set方法里面传过来的形参const void *key,value为ObjcAssociation对象。
ObjcAssociation对象中存储了set方法最后两个参数,policy和value。
所以set方法的四个参数的值都存入了表中:
所以set 和get 和remove 就是对这些的操作过程。
六 动态的增加方法
在消息发送时,如果在父类中也没有找到相应的IMP,就会执行resolveInstanceMethod方法,进行动态方法解析。在这个方法里面,我们可以动态的给类对象或者实例对象动态的增加方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
七 : NSCoding 自动归档 和解档。
用runtime实现的思路是:我们循环依次找到每个成员变量的名称,然后利用KVC读取和赋值就可以完成encodeWithCoder和initWithCoder了。
八: 字典模型互换
几个出名的开源库JSONModel、MJExtension等都是通过这种方式实现的。
字典转模型:
1.利用runtime的class_copyIvarList获取属性数组,
2.遍历模型对象的所有成员属性,根据属性名找到字典中key值进行赋值,当然这种方法只能解决NSString、NSNumber等,
3.如果含有NSArray或NSDictionary,还要进行第二步转换,如果是字典数组,需要遍历数组中的字典,利用objectWithDict方法将字典转化为模型,在将模型放到数组中,最后把这个模型数组赋值给之前的字典数组。
模型转字典步骤差不多。
9.runtime的缺点:
Method swizzling不是原子性操作。如果在+load方法里面写,是没有问题的,但是如果写在+initialize方法中就会出现一些奇怪的问题。
难以理解和debug。