死磕Objective-C runtime运行时之二

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

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

动态添加方法class_addMethod

第一种:

@interface ViewController()
- (void)hello:(NSString *)content;
@end

void 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"];

}

运行时objc_msgSend说过:

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:方法的,参考运行时objc_msgSend, 完成方法调用

第二种:

@interface ViewController ()
- (void)hello:(NSString *)content;
@end

void 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

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

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

消息转寄Forwarding

如果运行时objc_msgSend找不到该方法,在抛出异常之前,运行时给我们一个机会转寄(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, 首先之行运行时objc_msgSend

消息转寄(Forwarding)总结:

这里说的是运行时objc_msgSend不成功的时候:

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

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

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

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

问题一答案:

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

#import <objc/runtime.h>
@interface ViewController ()
- (void)hello:(NSString *)content;
- (void)whoareyou;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self hello:@"world"];
    [self whoareyou];
    
}
@end



IMP __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

320 声望
16 粉丝
0 条评论
推荐阅读
SE内功修炼之算法篇
今天整理下博客,没想到自己2017年5月份的时候就开始挑战Leetcode了,虽然挑战的难度都为简单级别,但还是在6月14日,以失败告终,战绩44道题PASS,9道题失败。

天才小飞猫阅读 1.3k

彻底搞懂EventLoop事件循环机制(浏览器和Node EventLoop)
前端的同学们应该都听说过EventLoop的概念,网上各大平台关于它的文章也是成百上千质量参差不一,其实在笔者刚开始接触js的时候这对这方面一头雾水,也是看了高程、官方文档以及大量的文章后才对它有了深刻认识,...

ihengshuai3阅读 369

封面图
iOS 健康共享失败如何解决
您要开始与之共享的对象必须已经连同他们的 iCloud 账户邮箱一起保存在您的“通讯录”中(iCloud 账户邮箱即 iCloud 账户绑定的邮箱信息,不是强制要求 @iCloud.com 邮箱)。

岚哲阅读 5k

探究 iOS 内存问题
本文从 Tagged Pointer、objc 源码、dealloc 原理、AutoreleasePool 原理、野指针探究等技术点展开聊了聊 iOS 内存相关问题。

杭城小刘1阅读 1.4k

封面图
iOS IDA逆向之patch
这里介绍的是ida的patch.1.搜索svc #0x80,回到IDA View-A界面,才能正确搜索点击图中T字按钮,弹出搜索框点击列表中进入2.修改svc #0x80,在IDA View-A界面中选中svc那一行,点击工具栏Edit--&gt;Patch program--&gt...

宋冬野阅读 2k

社交场景下iOS消息流交互层实践
一款社交产品的诞生,离不开即时通讯(IM)场景。随着团队业务版图在社交领域的布局,诞生了多个社交场景APP,涉及的IM场景,包含私聊、群聊、聊天室等。

云音乐技术团队1阅读 434

封面图
SegmentFault 思否技术周刊 Vol.77 — 探究关于 iOS 的特性
本文从 Tagged Pointer、objc 源码、dealloc 原理、AutoreleasePool 原理、野指针探究等技术点展开聊了聊 iOS 内存相关问题。

Beverly阅读 1.4k

封面图
320 声望
16 粉丝
宣传栏