认识Runtime运行时机制

OC方法的本质

首先了解OC方法的本质到底是什么:

OC方法由两个部分组成:

SEL: 方法编号(一本书的目录编号)
IMP: 方法实现,是函数指针,指向函数(一本书的目录页码,页码指向对应页的内容)

动态绑定

简单举个例子:一个Person类的.m文件中不实现-(void)eat:(NSString *)
通过运行时来动态实现这个eat方法,这个过程叫做 动态绑定

 #import <objc/message.h>
 
 +(BOOL)resolveInstanceMethod:(SEL)sel{
    // 给类添加eat方法,IMP==eat
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        //self:方法调用者,sel:方法编号,eat:(IMP函数指针)方法实现
        class_addMethod(self, sel, eat, “”);
    }
    return [super resolveInstanceMethod:sel];
  }
  
 // 实现c函数eat()
 void eat(id self, SEL _cmd, NSString *objc){
    NSLog(@”我来了%@”, objc);
 }

OC方法调用 会传递两个 隐式参数 self, _cmd,self 是方法的调用者,_cmd 是方法编号;
OC的方法调用其实是 消息发送 (通过终端clang –rewrite-objc main.m,生成一个.cpp文件,可以看到.m文件的底层实现。)

Person *p = [[Person alloc]init];   
//[P eat:@”汉堡”]; 的底层就是objc_msgSend函数
objc_msgSend(P, @selector(eat:), @”汉堡”);

消息转发

重定向

当对象的方法签名在头文件中暴漏出来,而在.m文件中忘记实现,一般程序会报运行时错误不识别的选择器,通过消息转发可以改变这行为。

消息转发: 当对象接收到与其 方法集 不匹配的消息时,通过消息转发机制可以使对象执行用户预先定义的逻辑,如:将消息发送给能够做出响应的其他接收器(对象),或者将所有无法识别的消息都发送给同一个
接收器 再或者 默默的吞下消息(既不执行处理过程也不使程序抛出运行时错误)。

还是上面的例子,在Dog类中实现了eat方法,在Person类中可以通过 消息转发 让Dog去相应eat(我吃不了,dog你帮我吃吧)

//消息重定向
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if ([_dog respondsToSelector:aSelector]) {  
        return _dog;     // 相当于 [_dog performSelector:aSelector];  
    }   
    // 给nil发消息          
   return nil; 
}

方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {  
    if(aSelector == @selector(eat:)) {  
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; // v@:@ (type encoding)  
    }  
    return nil;  
}  

- (void)forwardInvocation:(NSInvocation *)anInvocation {  
    if (anInvocation.selector == @selector(eat)) {  
        Dog *dog = [[Dog alloc] init];  
        [anInvocation invokeWithTarget:dog];  
    }  
} 

重写methodSignatureForSelector:和forwardInvocation:方法,将方法签名,转发给真正实现了该方法的目标对象,让其去调用已实现的方法。

methodSignatureForSelector:的作用在于为另一个类实现的消息创建一个有效的方法签名,必须实现,并且返回不为空的methodSignature,否则会crash
forwardInvocation:将选择器转发给一个真正实现了该消息的对象。

1.forwardingTargetForSelector同为消息转发,但在实践层面上有什么区别?何时可以考虑把消息下放到forwardInvocation阶段转发?

forwardingTargetForSelector 仅支持一个对象的返回,也就是说消息只能被转发给一个对象。比如转发给一个专门用于处理未识别的方法的处理类。
forwardInvocation 可以将消息同时转发给任意多个对象,如果你想执行其他逻辑(如记录日志并吞下该消息),可以考虑用 forwardInvocation

• 关于 signatureWithObjCTypes: 中的objcTypes,是OC的类型编码 Type Encodings【相关文档链接
• 语法参照图:

image.png

前面提到OC方法调用 会传递两个 隐式参数 self, _cmd,self 是方法的调用者,_cmd 是方法编号,指向方法本身。

例如:-(void)eat:(NSString *)food;实际上有三个参数:self, _cmd和food。

eat: 转ObjcTypes为:
"返回值类型 第一参数 第二参数 [第三参数...]"
如果没有返回值用v,如果有用@代替id类型,
第一二参数是必须存在的,即 id 类型的 self,和 SEL 类型的 _cmd,第三参数是用户自定义的参数,可有可无

例如: "v@:@" : void id类型的self SEL类型的_cmd 自定义参数;
"@@:" :id类型的返回值 id类型的self SEL类型的_cmd

因此我们可以调用[anInvocation getArgument: atIndex:] 获取指定的参数值

Runtime应用场景—HOOK(钩子)

HOOK,方法欺骗

直接上例子:

/*当url中含有中文时(需要转码),request还是能创建,但是此时  
  request中的url为空,request的创建方法没有检测url为空的情况,  
  很容易出现难以定位的bug(Swift中有可选类型,可以避免这问题)。
*/
NSURL *url = [NSURL URLWithString:@”http://www.baidu.com/中文”];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

解决:①用 category 创建分类 NSURL+HOOKXW_URLWithString 方法,将用到 URLWithString 的地方替换成我们自己的方法 XW_URLWithStringXW_URLWithString 保留了 URLWithString 原本内部创建 url 的方式,添加 url 是否为空的判断。
不足:每次都要去导入头文件,每次都要去替换项目中原本的urlWithString方法,比较麻烦;

②利用runtime运行时,改变方法调用的顺序。
OC发送 URLWithString 消息会对应的的去找这个方法的实现,用运行时可以去改变这种一一对应的关系,
只要HOOKURLWithString 这个方法的调用,当发送 URLWithString(SEL)消息时,让它去找 XW_URLWithString(IMP)这个实现。

NSURL+HOOK.m 文件中在 +(void)load 方法中下钩子HOOK,以交换方法的IMP实现。

#import<objc/runtime.h>

+(void)load {
    //获取method
    Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:)); 
    Method XWURLWithStr = class_getClassMethod(self, @selector(XW_URLWithString:));
    //交换方法的IMP
    method_exchangeImplementations(URLWithStr, XWURLWithStr);
}

外界不需要导入 NSURL+HOOK.h ,也不需要修改 URLWithStringXW_URLWithString 就能直接把 URLWithString 方法实现替换。

XW_URLWithString 实现如下:

+(void) XW_URLWithString:(NSString*)URLString{
    // 保留系统原本的实现,实现交换后这里不能用URLWithString,否则会递归
    NSURL *url = [NSURL XW_URLWithString:URLString];
    if(url == nil){
        NSLog(@"空了");
    }
    return url;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容