RunTime常用场景小结
RunTime简称运行时,基本用C和汇编写的。
1.消息转发机制;
2.通过分类扩展属性;
3.拦截系统方法调用(高大上的Swizzle 黑魔法),其实也是对系统的方法进行交换方法;
4.序列化和反序列;
下面我们具体来分析一下RunTime的使用场景;
1.消息转发机制:当调用某对象上某个方法(sendMessage),而该方法没有实现时,系统会报unrecognized selector的异常。在此异常之前走了一圈流程(如图),objc的运行时会给出三次拯救程序崩溃的机会。
第一次:
当这个类被调用一个没有实现的类方法 ,会调用+ (BOOL)resolveClassMethod:(SEL)sel或者+ (BOOL)resolveInstanceMethod:(SEL)sel,可以在提供一个函数实现;
//当这个类被调用了一个没有实现的对象方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"sendMessage:"]) {
// class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
//官方文档 command+shift+0
//cls name imp types
return class_addMethod(self, sel,(IMP)sendMessage,"aaa@qq.com:@");
}
return [super resolveInstanceMethod:sel];
}
void sendMessage(id self, SEL _cmd ,NSString *message){
NSLog(@"message ==%@",message);
}
第二次:
当第一次机会没有把握后会调用Fast forwarding 消息转发,此时的消息转发给其他的对象,只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成返回的那个对象(Son)。
// Fast forwarding 消息转发 第二步
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"]) {
return [Son new];
}
return [super forwardingTargetForSelector:aSelector];
}
第三次:
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时发出异常崩溃。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
//normal forwarding 消息转发 第三步
//越到后面消耗越大
//1
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"sendMessage:"])
{
return [NSMethodSignature signatureWithObjCTypes:"aaa@qq.com:@"];
}
return [super methodSignatureForSelector:aSelector];
}
//2
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = [anInvocation selector];
Son *son = [Son new];
if ([son respondsToSelector:sel]) {
[anInvocation invokeWithTarget:son];
return;
}
[super forwardInvocation:anInvocation];
}
//消息无法处理
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"消息无法处理");
}
2.通过分类扩展属性:分类中是无法设置属性,property只能为其生成get 和 set 方法的声明,但无法生成成员变量。所以需要借助Runtime为分类扩展属性功能。
//通过分类扩展属性
- (void)setIsSelected:(BOOL)isSelected
{
objc_setAssociatedObject(self, &selected, @(isSelected), OBJC_ASSOCIATION_ASSIGN);
}
- (BOOL)isSeted
{
return objc_getAssociatedObject(self, &selected);
}
3.拦截系统方法调用:主要函数method_exchangeImplementations,当我们需要改变一个系统方法的实现时,想修改方法的实现就可以用此方法。load在main之前调用,点击APP的时候就是把可以执行的读到内存里面。
//load在main之前调用 点击APP的时候就是把可以执行的读到内存里面
+ (void)load
{
//交换方法的调用顺序
Method urlStr = class_getClassMethod(self, @selector(URLWithString:));
Method WP_urlStr = class_getClassMethod(self, @selector(WP_URLWithString:));
//交换
method_exchangeImplementations(urlStr, WP_urlStr);
}
//类似于方法这样的高级功能
//协商注释!
+(instancetype)WP_URLWithString:(NSString *)URLString
{
//断点时会一点递归,此时已经交换了 所以调用WP就是调用系统消息
// NSURL *url = [NSURL URLWithString:URLString];
NSURL *url = [NSURL WP_URLWithString:URLString];
if (url == nil) {
NSLog (@"url is nil");
}
return url;
}
4.序列化和反序列:(归根到底还是遍历属性)
//序列化和反序列化
- (void)encodeWithCoder:(NSCoder *)aCoder;
{
unsigned int count = 0;
// 获取所有成员变量
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivars[i];
const char *name = ivar_getName(var);
// 将每个成员变量名转换为NSString对象类型
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self == [self init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[aDecoder setValue:value forKey:key];
}
}
return self;
}