Runtime——消息转发与运用

本文主要是针对runtime消息转发进行整理,并举例关于消息转发的运用。

消息转发

1、消息调用

OC中发送消息是通过objc_msgSend(id, SEL, ...) 来实现的,首先会根据isa所指向的类结构中进行方法查找(objc_method_list),如果该类中无法查找到所对应的方法,则会沿类结构中的超类指针super_class继续向上索引,直至NSObject类。一旦索引到对应方法则会向该方法传递receiver对应的数据结构,同时,为了优化索引速度,系统会缓存每次成功索引的方法名和实现地址到类结构中的objc_cache。后续的方法索引将优先索引缓存中的方法列表。

通过isa查找,流程如下:

if (没有找到cache、objc_method_list,向父类索引至NSObject类) {
则去实现了动态方法方法解析。
}
else if ( 如果没有实现动态方法解析或者解析失败并且实现了消息转发机制) {
进入消息转发流程
}
else 程序crash

2、动态方法解析

如果调用的是实例方法则会调起该方法

+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

如果调用的是类方法则会调起该方法

+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

举个🌰:

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
3、消息转发

未能向调用方法提供具体实现时即+ (BOOL)resolveInstanceMethod:(SEL)sel;或+ (BOOL)resolveClassMethod:(SEL)sel;返回值为NO。此时将转入消息转发流程。

- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");

这个方法将是消息转发的最后机会,我们可以利用它将原有的消息转发至另外的对象或者忽略。其中参数anInvocation是基于面向对象对原有方法调用的一层封装,包含了方法名、调用参数、方法签名等。
举个🌰:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];//避免未处理而导致的Crash
}

运用

随着iOS系统版本的更新,部分性能更优异或者可读性更高的API将有可能对原有API进行废弃与更替。因此在开发中经常需要考虑、判断版本,那是不是可以考虑用runtime来进行动态处理?
下面主要是针对适配iOS 11 contentInsetAdjustmentBehaviorautomaticallyAdjustsScrollViewInsets做栗子

1、新建一个Category(RTForwarding)

用于调用到iOS 11属性contentInsetAdjustmentBehavior的封装处理
代码如下:

//重写runtime方法
//1.为即将转发的消息返回一个对应的方法签名(该签名后面用于对转发消息对象(NSInvocation *)anInvocation进行编码用)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { // 1
    NSMethodSignature *signature = nil;
    
    if (aSelector == @selector(setContentInsetAdjustmentBehavior:)) {
        
        signature = [UIViewController instanceMethodSignatureForSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)];
    }else {
        
        signature = [super methodSignatureForSelector:aSelector];
    }
    
    return signature;
}
//2.开始消息转发((NSInvocation *)anInvocation封装了原有消息的调用,包括了方法名,方法参数等)
- (void)forwardInvocation:(NSInvocation *)anInvocation { // 2
    
    BOOL automaticallyAdjustsScrollViewInsets  = NO;
    UIViewController *topmostViewController = [self getTopmostViewController];
    //3.由于转发调用的API与原始调用的API不同,这里我们新建一个用于消息调用的NSInvocation对象viewControllerInvocatio并配置好对应的target与selector
    NSInvocation *viewControllerInvocation = [NSInvocation invocationWithMethodSignature:anInvocation.methodSignature]; // 3
    [viewControllerInvocation setTarget:topmostViewController];
    [viewControllerInvocation setSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)];
    //4.配置所需参数:由于每个方法实际是默认自带两个参数的:self和_cmd,所以我们要配置其他参数时是从第三个参数开始配置
    [viewControllerInvocation setArgument:&automaticallyAdjustsScrollViewInsets atIndex:2]; // 4
    //5.消息转发
    [viewControllerInvocation invokeWithTarget:topmostViewController]; // 5
}

//获取栈顶控制器
- (UIViewController *)getTopmostViewController {
    
    UIViewController *resultVC;
    resultVC = [self getTopmostViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
    while (resultVC.presentedViewController) {
        resultVC = [self getTopmostViewController:resultVC.presentedViewController];
    }
    return resultVC;
}
- (UIViewController *)getTopmostViewController:(UIViewController *)vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [self getTopmostViewController:[(UINavigationController *)vc topViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [self getTopmostViewController:[(UITabBarController *)vc selectedViewController]];
    } else {
        return vc;
    }
}
2、调用Category(RTForwarding)

在需要使用的地方导入头文件

#import "UIScrollView+RTForwarding.h"

在使用到iOS 11属性contentInsetAdjustmentBehavior时,不需要进行判断就可以实现之前需要判断功能。
代码如下:

    CGSize main = [UIScreen mainScreen].bounds.size;
    
    UITableView * tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, main.width, main.height - 64)];
    tableView.delegate = self;
    tableView.dataSource = self;
    tableView.backgroundColor = [UIColor orangeColor];
    tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//无需判断,简单粗暴
    [self.view addSubview:tableView];

理解了消息转发,在应用上还是能够出其不意达到简单粗暴的效果。本文主要是参考来源与链接,该文作者写的贼棒,本文主要是对自己的理解进行归纳整理,让自己的思路更清晰,当然如果有写的不对的地方欢迎指出。最后,当然也是最重要的附上demo地址

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