Objective-C语言是扩展于C语言的一种面向对象的编程语言,然而其方法的调用方式又和大多数面向对象语言大有不同,其采用消息传递、转发的方式进行方法的调用。因此在OC中,对象的真正行为往往发生在运行时确定而非编译时确定,所以OC有被称为一种运行时动态语言。

1、动态的Objective-C语言

我们将会对Objective-C语言的消息机制与运行时动态性进行探讨,包括消息传统与转发的基本原理、Objective-C函数调用的基本原理以及运行时的一些基础知识。

1.1消息转发机制

许多面向对象语言中方法的调用都采用obj.function这样的方式,在OC语言中却采用中括号包裹的方式,如[obj function]。实际上,OC中的每一个方法调用最后都会被转换成一条消息进行发送。

一条消息包含三部分:方法选择器、接收消息的对象及参数。objc_msgSend函数就是用来发送消息的。

如下,我们创建一个Person类,继承于NSObject,在Person类中添加个- (void)showName:(NSString *)name age:(NSInteger)age;方法,然后在调用该方法,代码如下:

- (void)showRunTimeSendMsg {

Person *p = [[Person alloc]init];

//常规调用方法

[p showName:@"Henry" age:20];

//发送消息

((void(*)(id,SEL,NSString *,NSInteger))objc_msgSend)(p,@selector(showName:age:),@"Jack",21);

}

打印信息:

image.png

运行工程,正常运行,且打印信息正常打印,说明发送消息成功。

1.2、消息传递与继承链

在介绍消息机制前,关于@selector()我们还需要深入理解下。通过@selector(方法名)可以获取到一个SEL类型的对象,SEL实际上是objc_selector结构体指针,在OC库头文件中没有找到对objc_selector结构体的定义,但我们可以合理猜测,其中很可能包含的是一个函数指针。

因此可以将SEL理解为函数签名,在程序的编译阶段,我们定义类中所有的方法会生成一个方法签名列表,这个列表是类直接关联的(从原则上来说,类的本质也是对象,它是一个单例对象),在运行时通过方法签名表来找到具体要执行的函数。

我们再来看objc_msgSend()函数,前面说过,它的第一个参数为接收消息的对象,第二个参数为方法签名,之后为传递的参数。那么OC运行时是如何根据一个对象实例来找到方法签名表,再找到要执行的方法呢?细心观察,你会发现所有的NSObject子类对象中都包含一个isa成员变量,这个isa变量时Class类型,我们的主角终于来了~ ~。Class顾名思义就是“类”类型,其实质是objc_class结构体指针:

typedef struct objc_property *objc_property_t;

struct objc_class {

//元类指针

Class _Nonnull isa OBJC_ISA_AVAILABILITY;

#if !__OBJC2__

//父类

Class _Nullable super_class OBJC2_UNAVAILABLE;

//类名

const char * _Nonnull name OBJC2_UNAVAILABLE;

//类的版本

long version OBJC2_UNAVAILABLE;

//信息

long info OBJC2_UNAVAILABLE;

//内存布局

long instance_size OBJC2_UNAVAILABLE;

//变量列表

struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;

//函数列表

struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;

//缓存方式

struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;

//协议列表

struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;

#endif

} OBJC2_UNAVAILABLE;

每一个类对象都有一个isa指针,这个指针指向的类实际上是元类,即构造“类”的类。我们也无需纠结这些概念,举个简单的例子:在OC中有加方法和减方法,减方法是实例对象调用的方法,在每一个“类”中都包含一个函数列表,就是上面的objc_method_list结构体数组指针,同样如果调用加方法,实际上是从类的元类中找到对应的方法列表,这个列表就是我们前面提到的方法签名列表,进行方法的执行。关于实例对象、“类”对象和元类,下图很好的表现了它们间的关系:

image.png

上面介绍的消息发送机制其实十分不完整,OC是支持继承的,因此如果在当前对象的类的方法列表中没有找到此消息对应的方法签名,则系统会通过Superclass一层层继续向上,直到找到相应的方法或者到达继承链的顶端。

1.1.3、拯救未知消息的三根救命稻草

因此我们可以深入的分析消息的传递过程,如果消息的接收对象可以处理这个消息,即在其isa指针对应的类中可以查找到这个方法,那找到对应方法直接执行。如果接收对象无法处理,其父类、父父类等都无法处理,如果出现这种情况,OC为了增强语言的动态性,程序并不会马上Crash,在Crash前,有三次机会可以挽救。

第一根救命稻草:

如上所述,如果对象的整个继承链都无法处理当前消息,那么首先会调用接收对象所属的+ (BOOL)resolveInstanceMethod:(SEL)sel 方法(对应实例方法),+ (BOOL)resolveClassMethod:(SEL)sel方法(对应类方法),这个方法中,开发者有机会为类动态添加方法。如果动态添加了方法,则可以在这个方法中返回YES,那么这条消息依然会被成功处理。例如:

#import

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

- (void)showSelf;

@end

NS_ASSUME_NONNULL_END

#import "Person.h"

#import

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {

if ([NSStringFromSelector(sel) isEqualToString:@"showSelf"]) {

SEL aSel = NSSelectorFromString(@"showMy");

Method aMethod = class_getInstanceMethod(self, aSel);

class_addMethod(self, sel, method_getImplementation(aMethod), "v@:");

return YES;

}

return [super resolveInstanceMethod:sel];

}

- (void)showMy {

NSLog(@"showMy");

}

@end

当我们调用showSelf方法时,发现程序可以正常运行。

IMP和SEL并不同,SEL可以理解为函数签名,其与函数名相关联,而IMP是函数所在地址的指针。简单理解,通过IMP我们可以直接拿到函数的地址。

第二根救命稻草

当通过运行时添加方法被否定后,系统会接着调用forwardingTargetForSelector方法,这个方法用来对消息进行转发,在OC中强大的消息转发机制的奥秘就在这里。forwardingTargetForSelector方法需要返回一个id类型的对象,系统会将当前对象服务处理的消息转发给这个方法返回的对象,如果这个返回的对象可以处理,那么程序依然可以执行。示例如下:

我们在Dog类中添加方法和实现 showSelf

#import

NS_ASSUME_NONNULL_BEGIN

@interface Dog : NSObject

- (void)showSelf;

@end

NS_ASSUME_NONNULL_END

#import "Dog.h"

@implementation Dog

- (void)showSelf {

NSLog(@"showSelf In DogClass");

}

@end

在Person类中实现如下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector {

if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {

return [Dog new];

}

return [super forwardingTargetForSelector:aSelector];

}

forwardingTargetForSelector方法可以返回一个对象,OC会将当前对象无法处理的消息转发给这个方法返回的对象。如果返回nil,则表示不进行消息转发,那么就要用到第三根救命稻草了。

第3根救命稻草

如果错过了前面的动态添加方法和快速转发两根救命稻草,那么我们还有最后一次机会。系统会调用methodSignatureForSelector方法,这个方法的主要用途是询问这个选择器是否是有效的,我们需要返回一个NSMethodSignature对象,这个对象是函数签名的抽象。

如果我们返回了有效的函数签名,那么接着系统会调用forwardInvocation方法,这就是拯救程序的最后一根稻草了,这个函数会直接将消息包装成NSInvocation对象传入,我们直接将其发送给可以处理此消息的对象即可。示例如下:

//询问此选择器是否有效

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {

return [[Dog new] methodSignatureForSelector:aSelector];

}

return [super methodSignatureForSelector:aSelector];

}

//处理消息

- (void)forwardInvocation:(NSInvocation *)anInvocation {

if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"showSelf"]) {

[anInvocation invokeWithTarget:[Dog new]];

}else {

[super forwardInvocation:anInvocation];

}

}

1.4你真的需要救命稻草吗

通过上面的接收,我们对OC的消息机制有了全面深入的了解。正常情况下我们不会使用到这些函数的。

最后如果我们没有使用上面的救命稻草,那么当向某个对象发送无法处理的消息时,系统最终会调用到NSObjce类的doseNotRecognizeSelector方法,这个方法会抛出异常。我们可以重写该方法自定义输出:

- (void)doesNotRecognizeSelector:(SEL)aSelector {

NSLog(@"not have a method name %@",NSStringFromSelector(aSelector));

if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {

return;

}

[super doesNotRecognizeSelector:aSelector];

}

ios runtime重要性_iOS运行时RunTime详解相关推荐

  1. ios runtime重要性_iOS:学习runtime的理解和心得

    作者:兴宇是谁 授权本站转载. Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西.最近在学习Runtime,有自己的一些心得,整理如下, 一为 查阅方便 二为 ...

  2. Deep Learning部署TVM Golang运行时Runtime

    Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...

  3. “ compiler-rt”运行时runtime库

    " compiler-rt"运行时runtime库 编译器-rt项目包括: • Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接 ...

  4. CUDA运行时 Runtime(四)

    CUDA运行时 Runtime(四) 一. 图 图为CUDA中的工作提交提供了一种新的模型.图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义.这允许定义一次图形,然后重复启动.将图的定义 ...

  5. CUDA运行时Runtime(三)

    CUDA运行时Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的存 ...

  6. CUDA运行时 Runtime(二)

    CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...

  7. CUDA运行时 Runtime(一)

    CUDA运行时 Runtime(一) 一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和 libcudart.a,或动态通过cudart.dll或者lib ...

  8. iOS 7: iPhone/iPad应用开发技术详解

    iOS 7: iPhone/iPad应用开发技术详解 作者:刘一道 出版社:机械工业出版社 出版年:2013-11 页数:507 定价:79.00元 ISBN:9787111440512 样章下载:h ...

  9. iOS 7 iPhone iPad应用开发技术详解

    2019独角兽企业重金招聘Python工程师标准>>> iOS 7 iPhone iPad应用开发技术详解 本书内容全面,从 Objective-C语法知识.iOS功能特 性,到高级 ...

最新文章

  1. linux 图形界面 X Server 关闭 启动
  2. 【解决方案】在Pycharm使用jupyter要求填写token【please enter your jupyter notebook url】
  3. 面试被问到“零拷贝”!你真的理解吗?
  4. Redis是如何写代码注释的?
  5. pyspark distinct代码示例
  6. java序列化如何实现_Java实现序列化与反序列化的简单示例
  7. go tool trace goalng调优工具
  8. mysql 日期滞后_如何滞后MySQL中的列?
  9. Qt样例学习1(数字时钟)
  10. spring.net 中配置文件分开储存的写法
  11. idea中.ignore插件的使用
  12. Kotlin(android)协程中文翻译
  13. 如果在文档已完成加载后执行 document.write,整个 HTML 页面将被覆盖
  14. Chukwa在百度的应用实践
  15. 自定义封装无人值守Windows10镜像
  16. access建立er图_关于ER图的快速生成 | 学步园
  17. python画二次函数图像的顶点坐标为_Python绘制函数图像
  18. ARCore之路-平面检测
  19. django 开发(一) mezzanine源码+ubuntu实现CMS Demo
  20. 杭州河坊街特色手机饰品——招财猫!

热门文章

  1. 如何选择CMWAP或CMNET进行拨号
  2. webconfig machineKey
  3. 纯css 使元素像画卷一样展开或者横向展开
  4. 【集成电源开关拓扑系列一】
  5. 面向对象之远哥法师和战士组队游戏管理操作系统
  6. 互联网早报:货拉拉宣布上线行程录音功能,试运行车载设备后将逐步推广至全国
  7. 给宝宝起名要遵循的几点建议
  8. CSS3新增样式-borderRadius的使用
  9. VMware虚拟机设置固定IP (Win10 + ubuntu 16.04)
  10. 前端常用英文,持续更新中