资讯专栏INFORMATION COLUMN

死磕Objective-C runtime运行时之二

Ku_Andrew / 1417人阅读

摘要:实战问题一答案在最后如何吞掉整个应用的崩溃,使程序正常运行动态添加方法第一种运行时说过方法只不过是语言方方法,加上两个特殊的参数第一个是,第二个参数是这里我们有个函数,除了上述两个必要参数外,我们添加了个然后用添加,最后一个参数是运行时

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

如何吞掉整个应用的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不成功的时候:

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

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

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

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

问题一答案:

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

#import 
@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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/16537.html

相关文章

  • 死磕Objective-C runtime运行时之

    摘要:说到运行时,如果你还不清楚,那可要看仔细了,如果你是靠颜值而不是才华能够顺利通过面试,喵了个咪的,我也想去试试运行时出现时就是运行时版本了,和旧的相比拥有两大特性第一,就是修改,增加或删除一个类的实例变量,不用再重新编译子类就可以用了。 说到Objc运行时,如果你还不清楚,那可要看仔细了,如果你是靠颜值而不是才华能够顺利通过面试,喵了个咪的,我也想去试试 Objc运行时2.0 iOS出...

    LiangJ 评论0 收藏0
  • iOS 项目统计图片使用情况

    摘要:文章转自项目统计图片使用情况随着项目开发推进和版本迭代,项目中总会存在一些无效的图片资源,这些无效图片往往会增加编译成本和包的大小。一个普通的工程会有大约三分之一的图片是未使用的。 文章转自:https://blog.coding.net/blog/statistics-of-IOS-project-pictures iOS项目统计图片使用情况 随着项目开发推进和版本迭代,项目中总会存...

    selfimpr 评论0 收藏0
  • 实战Java虚拟机之二“虚拟机的工作模式”

    摘要:今天开始实战虚拟机之二虚拟机的工作模式。总计有个系列实战虚拟机之一堆溢出处理实战虚拟机之二虚拟机的工作模式实战虚拟机之三的新生代实战虚拟机之四禁用实战虚拟机之五开启编译目前的虚拟机支持和两种运行模式。 今天开始实战Java虚拟机之二:虚拟机的工作模式。 总计有5个系列实战Java虚拟机之一堆溢出处理实战Java虚拟机之二虚拟机的工作模式实战Java虚拟机之三G1的新生代GC实战Jav...

    focusj 评论0 收藏0
  • Swift 中的 Runtime

    摘要:更确切地说,可能在仅使用库的时候只运行。比如说中的就无法向既有类添加属性。在讲解的原文中曾指出出于安全性和一致性的考虑,方法交叉过程永远会在方法中进行。随便修改基础框架或所使用的三方代码会给项目造成很大的影响。 即使在 Swift APP 中没有一行 Object-c 的代码,每个 APP 也都会在 Object-c runtime 中运行,为动态任务分发和运行时对象关联开启了一个世界...

    guqiu 评论0 收藏0
  • Swift 中的 Runtime

    摘要:更确切地说,可能在仅使用库的时候只运行。比如说中的就无法向既有类添加属性。在讲解的原文中曾指出出于安全性和一致性的考虑,方法交叉过程永远会在方法中进行。随便修改基础框架或所使用的三方代码会给项目造成很大的影响。 即使在 Swift APP 中没有一行 Object-c 的代码,每个 APP 也都会在 Object-c runtime 中运行,为动态任务分发和运行时对象关联开启了一个世界...

    miracledan 评论0 收藏0

发表评论

0条评论

Ku_Andrew

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<