原文地址:http://www.jianshu.com/p/f6300eb3ec3d

一、关于runtime


之前在项目中有遇到过用runtime解决改变全局字体的问题,所以再一次感受到了runtime黑魔法的强大,趁现在有机会分享一下对runtime的一些理解。
在对象调用方法是Objective-C中经常使用的功能,也就是消息的传递,而Objective-CC的超集,所以和C不同的是,Objective-C使用的是动态绑定,也就是runtimeObjective-C的消息传递和消息机制也就不多说了,今天主要说的是动态方法,也就是函数的调用。

二、相关的几个函数


下面一张图详细的概括了每个函数调用的先后以及执行的前提

消息传递函数的调用
  • 1.对象在收到无法解读的消息后,首先会调用所属类的

+ (BOOL)resolveInstanceMethod:(SEL)sel

这个方法在运行时,没有找到SELIML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。根据文档,如果实现了添加函数代码则返回YES,未实现返回NO
举个例子,新建了一个工程,首先我在ViewController这个类中执行doSomething1这个方法,代码如下

  1. //
  2. // ViewController.m
  3. // RuntimeTest1
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. @interface ViewController ()
  10. @end
  11. @implementation ViewController
  12. - (void)viewDidLoad {
  13. [super viewDidLoad];
  14. [self performSelector:@selector(doSomething)];
  15. }
  16. - (void)didReceiveMemoryWarning {
  17. [super didReceiveMemoryWarning];
  18. // Dispose of any resources that can be recreated.
  19. }
  20. @end

运行结果

  1. **2015-12-24 10:35:37.726 RuntimeTest1[1877:337842] -[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680**
  2. **2015-12-24 10:35:37.729 RuntimeTest1[1877:337842] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7fe9f3736680'**
  3. ***** First throw call stack:**

不出意外,程序崩溃,因为没有找到doSomething这个方法,下面我们在里面实现 + (BOOL)resolveInstanceMethod:(SEL)sel这个方法,并且判断如果SEL 是doSomething那就输出add method here

  1. //
  2. // ViewController.m
  3. // RuntimeTest1
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. @interface ViewController ()
  10. @end
  11. @implementation ViewController
  12. - (void)viewDidLoad {
  13. [super viewDidLoad];
  14. [self performSelector:@selector(doSomething)];
  15. }
  16. + (BOOL)resolveInstanceMethod:(SEL)sel {
  17. if (sel == @selector(doSomething)) {
  18. NSLog(@"add method here");
  19. return YES;
  20. }
  21. return [super resolveInstanceMethod:sel];
  22. }
  23. - (void)didReceiveMemoryWarning {
  24. [super didReceiveMemoryWarning];
  25. // Dispose of any resources that can be recreated.
  26. }
  27. @end

继续运行,然后看到log

  1. **2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] add method here**
  2. **2015-12-24 10:47:24.687 RuntimeTest1[2007:382077] -[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0**
  3. **2015-12-24 10:47:24.690 RuntimeTest1[2007:382077] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController doSomething]: unrecognized selector sent to instance 0x7f9568c331f0'**
  4. ***** First throw call stack:**

可以看到程序依然是崩溃了,但是我们可以看到输出了add method here,这说明我们 + (BOOL)resolveInstanceMethod:(SEL)sel这个方法执行了,并进入了判断,所以,在这儿,我们可以做一下操作,使这个方法得到相应,不至于走到最后- (void)doesNotRecognizeSelector:(SEL)aSelector这个方法中而崩掉了,接下来,我么继续操作,如下

  1. //
  2. // ViewController.m
  3. // RuntimeTest1
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import <objc/runtime.h>
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15. [self performSelector:@selector(doSomething)];
  16. }
  17. + (BOOL)resolveInstanceMethod:(SEL)sel {
  18. if (sel == @selector(doSomething)) {
  19. NSLog(@"add method here");
  20. class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
  21. return YES;
  22. }
  23. return [super resolveInstanceMethod:sel];
  24. }
  25. void dynamicMethodIMP (id self, SEL _cmd) {
  26. NSLog(@"doSomething SEL");
  27. }
  28. - (void)didReceiveMemoryWarning {
  29. [super didReceiveMemoryWarning];
  30. // Dispose of any resources that can be recreated.
  31. }
  32. @end

导入了<objc/runtime.h>并且在+ (BOOL)resolveInstanceMethod:(SEL)sel中执行了class_addMethod这个方法,然后定义了一个void dynamicMethodIMP (id self, SEL _cmd)这个函数,运行工程,看log

  1. **2015-12-24 11:45:11.934 RuntimeTest1[2284:478571] add method here**
  2. **2015-12-24 11:45:11.934 RuntimeTest1[2284:478571] doSomething SEL**

这时候我们发现,程序并没有崩溃,而且还输出了doSomething SEL,这时候就说明我们已经通过runtime成功的向我们这个类中添加了一个方法。关于class_addMethod这个方法,是这样定义的

  1. OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
  • cls 在这个类中添加方法,也就是方法所添加的类
  • name 方法名,这个可以随便起的
  • imp 实现这个方法的函数
  • types定义该数返回值类型和参数类型的字符串,这里比如"v@:",其中v就是void,带表返回类型就是空,@代表参数,这里指的是id(self),这里:指的是方法SEL(_cmd),比如我再定义一个函数

    1. int newMethod (id self, SEL _cmd, NSString *str) {
    2. return 100;
    3. }

    那么添加这个函数的方法就应该是
    ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");

  • 2.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法

消息继续往下传递到- (id)forwardingTargetForSelector:(SEL)aSelector看看是不是有对象可以执行这个方法,我们来重新建个工程,然后新建一个叫SecondViewController的类,里面有一个- (void)secondVCMethod方法,如下

  1. //
  2. // SecondViewController.m
  3. // RuntimeTest2
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "SecondViewController.h"
  9. @interface SecondViewController ()
  10. @end
  11. @implementation SecondViewController
  12. - (void)viewDidLoad {
  13. [super viewDidLoad];
  14. // Do any additional setup after loading the view.
  15. }
  16. - (void)secondVCMethod {
  17. NSLog(@"This is secondVC method !");
  18. }
  19. - (void)didReceiveMemoryWarning {
  20. [super didReceiveMemoryWarning];
  21. // Dispose of any resources that can be recreated.
  22. }
  23. /*
  24. #pragma mark - Navigation
  25. // In a storyboard-based application, you will often want to do a little preparation before navigation
  26. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  27. // Get the new view controller using [segue destinationViewController].
  28. // Pass the selected object to the new view controller.
  29. }
  30. */
  31. @end

工程结构应该是这样的

工程目录图

现在我想在ViewController中调用- (void)secondVCMethod这个方法,我们知道ViewControllerSecondViewController并无继承关系,按照正常的步骤去做程序肯定会因为在ViewController找不到- (void)secondVCMethod这个方法而直接崩溃的

  1. //
  2. // ViewController.m
  3. // RuntimeTest2
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import <objc/runtime.h>
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15. [self performSelector:@selector(secondVCMethod)];
  16. }
  17. - (void)didReceiveMemoryWarning {
  18. [super didReceiveMemoryWarning];
  19. // Dispose of any resources that can be recreated.
  20. }
  21. @end

运行结果

  1. **2015-12-24 13:54:44.314 RuntimeTest2[3164:835814] -[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10**
  2. **2015-12-24 13:54:44.317 RuntimeTest2[3164:835814] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController secondVCMethod]: unrecognized selector sent to instance 0x7fc3a8535c10'**
  3. ***** First throw call stack:**

现在我们来处理一下这个消息,如下

  1. //
  2. // ViewController.m
  3. // RuntimeTest2
  4. //
  5. // Created by HenryCheng on 15/12/24.
  6. // Copyright © 2015年 www.igancao.com All rights reserved.
  7. //
  8. #import "ViewController.h"
  9. #import <objc/runtime.h>
  10. @interface ViewController ()
  11. @end
  12. @implementation ViewController
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15. [self performSelector:@selector(secondVCMethod)];
  16. }
  17. - (id)forwardingTargetForSelector:(SEL)aSelector {
  18. Class class = NSClassFromString(@"SecondViewController");
  19. UIViewController *vc = class.new;
  20. if (aSelector == NSSelectorFromString(@"secondVCMethod")) {
  21. NSLog(@"secondVC do this !");
  22. return vc;
  23. }
  24. return nil;
  25. }
  26. + (BOOL)resolveInstanceMethod:(SEL)sel {
  27. return [super resolveInstanceMethod:sel];
  28. }
  29. - (void)didReceiveMemoryWarning {
  30. [super didReceiveMemoryWarning];
  31. // Dispose of any resources that can be recreated.
  32. }
  33. @end

运行结果

  1. **2015-12-24 14:00:34.168 RuntimeTest2[3284:870957] secondVC do this !**
  2. **2015-12-24 14:00:34.169 RuntimeTest2[3284:870957] This is secondVC method !**

我们会发现- (void)secondVCMethod这个方法执行了,程序也并没有崩溃,原因就是在这一步

  1. - (id)forwardingTargetForSelector:(SEL)aSelector {
  2. Class class = NSClassFromString(@"SecondViewController");
  3. UIViewController *vc = class.new;
  4. if (aSelector == NSSelectorFromString(@"secondVCMethod")) {
  5. NSLog(@"secondVC do this !");
  6. return vc;
  7. }
  8. return nil;
  9. }

在没有找到- (void)secondVCMethod这个方法的时候,消息继续传递,直到- (id)forwardingTargetForSelector:(SEL)aSelector,然后我在里面创建了一个SecondViewController的对象,并且判断如过有这个方法,就返回SecondViewController的对象。这个函数就是消息的转发,在这儿我们成功的把消息传给了SecondViewController,然后让它来执行,所以就执行了那个方法。同时,也相当于完成了一个多继承!

三、最后一点


当然,还有好几个函数,在上面那张图里面已经清晰的表达了,有兴趣的可以自己试试,看看消息的传递顺序到底是怎么样的。上面提到的这些知识runtime的冰山一角,runtime黑魔法的强大远不止于此,比如方法的调配(Method Swizzling)等,在项目实战中还是很有用的,后面有时间会再介绍.

参考

[转]runtime 消息机制的更多相关文章

  1. runtime——消息机制

    本文授权转载,作者:Sindri的小巢(简书) 从异常说起 我们都知道,在iOS中存在这么一个通用类类型id,它可以用来表示任何对象的类型 —— 这意味着我们使用id类型的对象调用任何一个方法,编译器 ...

  2. ios学习路线—Objective-C(Runtime消息机制)

    RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何 ...

  3. runtime - 消息机制

    Xcode中使用runtime代码时,建议先做下配置: 使用runtime代码时会有适当的提醒. OC方法调用的本质是消息转发,消息机制的本质 创建一个Person类,添加方法 - (void)eat ...

  4. iOS开发runtime学习:一:runtime简介与runtime的消息机制

    一:runtime简介:也是面试必须会回答的部分 二:runtime的消息机制 #import "ViewController.h" #import <objc/messag ...

  5. Objective-C总Runtime的那点事儿(一)消息机制

    最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎必问,例 如:RunLoop,Block,内存管理等.其他的问题 ...

  6. Objective-C总Runtime的那点事儿(一)消息机制【转】

    RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何 ...

  7. NSObject头文件解析 / 消息机制 / Runtime解读 (一)

    NSObject头文件解析 当我们需要自定义类都会创建一个NSObject子类, 比如: #import <Foundation/Foundation.h> @interface Clas ...

  8. 快速上手Runtime(一)之消息机制

    Runtime简介 Runtime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于动态 ...

  9. NSObject头文件解析 / 消息机制 / Runtime解读 (二)

    本章接着NSObject头文件解析 / 消息机制 / Runtime解读(一)写 给类添加属性: BOOL class_addProperty(Class cls, const char *name, ...

随机推荐

  1. Dark Mobile Bank之移动银行应用仿冒攻击威胁分析报告

    一.背景 据“第十五次全国信息网络安全状况暨计算机和移动终端病毒疫情调查”调查结果显示,2015年移动终端的病毒感染比例为50.46%,相对于2014年增长了18.96%,移动终端病毒感染率涨幅较大, ...

  2. 11.Object方法

    综述 Object是Java中所有类的父类,对它的学习十分的重要, Object的函数除了final方法,基本上都是被设计为要被覆盖的(Override),这节我们就一起来学习这些函数. 1.equa ...

  3. linux简单命令

    查看服务器开启的进程信息[root@CentOSHT ~]# top 其中第一行的  Load average 参数是服务器负载的意思,

  4. 简单谈谈NFC(转载自-tlex/pku_android)

    NFC是Near Field Communication缩写,又称近距离无线通信,是一种短距离的高频无线通信技术,允许电子设备之间进行非接触式点对点数据传输(在十厘米内)交换数据.这个技术由免接触式射 ...

  5. 学习微信小程序之css4设置颜色,单位表示,字体样式

    颜色的设置可以通过RGB设置 可以直接通过英文单词设置 可以通过16进制来设置 长度单位: 字体样式: 设置字体样式 字体粗细 设置字体风格 设置字间距

  6. sencha touch的开源插件和例子

    写了好久的sencha touch,没想到换工作竟然一年多没有搞了.因为项目的缘故收集了好多的组件,由于懒惰,没有整理,现在想想有点后悔了,再加上如果就这样丢弃,感觉有些遗憾,今天整理了一下放在git ...

  7. http tcp udp ip 间的关系

    首先,我自己梳理一下,其实除了应对以后的笔试,还有需要应对的是自己在编程中对于api的选择,我在满足需求时采取哪种方案更好. 首先,我需要了解的是tcp/ip是一个协议组,有三大层: ip 对应于网络 ...

  8. ACM: Gym 101047M Removing coins in Kem Kadrãn - 暴力

     Gym 101047M Removing coins in Kem Kadrãn Time Limit:2000MS     Memory Limit:65536KB     64bit IO Fo ...

  9. View 与 Controller 之间的delegate(代理)传值

    这个代理传值是经常使用的一种传值方式,下面介绍一种View 和 Controller 之间的代理传值方法. 先建立一个View视图 如 LoginView 是继承于一个UIView 在LoginVie ...

  10. js实现输入框数量加减【转】

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...