博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
死磕Objective-C runtime运行时之二
阅读量:6090 次
发布时间:2019-06-20

本文共 5131 字,大约阅读时间需要 17 分钟。

实战问题一(答案在最后):

如何吞掉整个应用的unrecognized selector sent to instance崩溃, 使程序正常运行?

动态添加方法class_addMethod

第一种:

@interface ViewController()- (void)hello:(NSString *)content;@endvoid hello(id self, SEL selector, NSString *content){    NSLog(@"hello %@", content);}@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    class_addMethod([self class], @selector(hello:), (IMP)hello, "v@:@");    [self hello:@"world"];}

说过:

Bbjc方法只不过是C语言方方法,加上两个特殊的参数第一个是receiver(self),第二个参数是selector(_cmd)

这里我们有个C函数void hello(id self, SEL selector, NSString *content),除了上述两个必要参数外,我们添加了个NSString *content, 然后用class_addMethod添加,最后一个参数是Objc运行时符号,具体参考, 第一个V代表返回值void, @代表id,:代表SEL,@代表id(这里是NSString *)

这里由于在调用[self hello:@"world"]之时, 运行时方法class_addMethod添加了hello:方法的,参考, 完成方法调用

第二种:

@interface ViewController ()- (void)hello:(NSString *)content;@endvoid hello(id self, SEL selector, NSString *content){    NSLog(@"hello %@", content);}@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    [self hello:@"world"];}+ (BOOL)resolveInstanceMethod:(SEL)sel{    if (sel == @selector(hello:)) {        class_addMethod([self class], sel, (IMP)hello, "v@:@");        return YES;    }    return [super resolveInstanceMethod:sel];}@end

这回,我们先调用方法,由于通过,无法找到hello:, 此时运行时会发消息给resolveInstanceMethod:resolveClassMethod:方法,本文因为成员方法调用[[self class] resolveInstanceMethod:@selector(hello:)],询问该方法是不是有可能动态实现呢,根据第二种代码实现,发现如果是@selector(hello:), 加入动态方法,然后return YES给运行时,告知可以处理该方法。

过程中,我猜测运行时会关注class_addMethod等相关功能代码,然后快速派遣,即使return YES, 假如此类状态没有任何变化,直接调用doesNotRecognizeSelector:抛出异常

消息转寄Forwarding

如果找不到该方法,在抛出异常之前,运行时给我们一个机会转寄(Forwarding)这个消息的机会:

第一种:

@interface SomeFool: NSObject- (void)hello:(NSString *)content;@end@implementation SomeFool- (void)hello:(NSString *)content{    NSLog(@"hello %@", content);}@end@interface ViewController (){    SomeFool *_surrogate;}- (void)hello:(NSString *)content;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];        [self hello:@"world"];}- (id)forwardingTargetForSelector:(SEL)aSelector{    if (!_surrogate) {        _surrogate = [SomeFool new];    }    return _surrogate;}@end

第一次机会是运行时询问forwardingTargetForSelector:是不是这个方法其他人能够处理呢?代码forwardingTargetForSelector:返回SomeFool实例,运行时就不会抱怨,把消息传给SomeFool实例啦

第二种:

@interface SomeFool: NSObject- (void)hello:(NSString *)content;@end@implementation SomeFool- (void)hello:(NSString *)content{    NSLog(@"hello %@", content);}@end@interface ViewController (){    SomeFool *_surrogate;}- (void)hello:(NSString *)content;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    _surrogate = [SomeFool new];    [self hello:@"world"];}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{        NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];    if (!signature) {        signature = [_surrogate methodSignatureForSelector:aSelector];    }    return signature;}- (void)forwardInvocation:(NSInvocation *)anInvocation{    if ([_surrogate respondsToSelector: [anInvocation selector]]){        [anInvocation invokeWithTarget:_surrogate];    }    else{        [super forwardInvocation:anInvocation];    }}

第二次机会是运行时询问methodSignatureForSelector:是不是这个方法其他人有具体实现呢?代码中我们把SomeFool将具体实现返回,然后运行时就会调用

forwardInvocation:。在其中,我们用[anInvocation invokeWithTarget:_surrogate]调用方法。直接将消息重新跑给SomeFool, 首先之行

消息转寄(Forwarding)总结:

这里说的是不成功的时候:

  1. resolveClassMethod:resolveInstanceMethod, 若返回YES同时运行时状态有新函数加入,则直接调用实现,完成消息发送

  2. 若不然, forwardingTargetForSelector: 若返回不是nil和self,则完成消息发送, 对返回对象进行

  3. 若不然, methodSignatureForSelector: 若返回不为空,则发送消息给forwardInvocation:由Invocation完成

  4. 若不然, 调用doesNotRecognizeSelector:抛出异常

问题一答案:

PS: 本例为Swizzle的正确打开方式,

#import 
@interface ViewController ()- (void)hello:(NSString *)content;- (void)whoareyou;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self hello:@"world"]; [self whoareyou]; }@endIMP __original_forwardInvocation = NULL;IMP __original_methodSignatureForSelector = NULL;void __swizzle_forwardInvocation(id self, SEL _cmd, NSInvocation * anINvocation){ //pretend nothing happen}NSMethodSignature * __swizzle_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector){ return ((NSMethodSignature *(*)(id, SEL, SEL))__original_methodSignatureForSelector)(self, _cmd, NSSelectorFromString(@"cacheAll"));}@interface NSObject(cacheAll)@end@implementation NSObject(cacheAll)+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oringal_method = class_getInstanceMethod([self class], @selector(forwardInvocation:)); __original_forwardInvocation = method_setImplementation(oringal_method, (IMP)__swizzle_forwardInvocation); oringal_method = class_getInstanceMethod([self class], @selector(methodSignatureForSelector:)); __original_methodSignatureForSelector = method_setImplementation(oringal_method, (IMP)__swizzle_methodSignatureForSelector); });}- (void)cacheAll{}@end

转载地址:http://zqvwa.baihongyu.com/

你可能感兴趣的文章
Linux中的帮助功能
查看>>
针对Android的Pegasus恶意软件版本和针对iOS的有什么不同?
查看>>
全局探色器
查看>>
Hive Export和Import介绍及操作示例
查看>>
http://mongoexplorer.com/ 一个不错的 mongodb 客户端工具。。。
查看>>
上传jar包到nexus私服
查看>>
Why Namespace? - 每天5分钟玩转 OpenStack(102)
查看>>
Project:如何分析项目中的资源分配情况
查看>>
HDU 4803 Poor Warehouse Keeper (贪心+避开精度)
查看>>
小错误汇总
查看>>
Spring源码系列 — Envoriment组件
查看>>
java正则表达式去除html标签,Java中正则表达式去除html标签
查看>>
使用Cobbler批量部署Linux操作系统
查看>>
zabbix企业应用之服务端与客户端的安装
查看>>
实例讲解遗传算法——基于遗传算法的自动组卷系统【理论篇】
查看>>
无法在web服务器上启动调试。调试失败,因为没有启用集成windows身份验证
查看>>
Bat相关的项目应用
查看>>
Django为数据库的ORM写测试例(TestCase)
查看>>
web.xml中的contextConfigLocation在spring中的作用
查看>>
NYOJ-107 A Famous ICPC Team
查看>>