死磕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"];
}
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 <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
渴望越狱的猫
SE内功修炼之算法篇
天才小飞猫阅读 1.3k
彻底搞懂EventLoop事件循环机制(浏览器和Node EventLoop)
ihengshuai赞 3阅读 369
iOS 健康共享失败如何解决
岚哲阅读 5k
探究 iOS 内存问题
杭城小刘赞 1阅读 1.4k
iOS IDA逆向之patch
宋冬野阅读 2k
社交场景下iOS消息流交互层实践
云音乐技术团队赞 1阅读 434
SegmentFault 思否技术周刊 Vol.77 — 探究关于 iOS 的特性
Beverly阅读 1.4k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。