理解Objective-C Runtime

2020-09-10  本文已影响0人  你duck不必呀
配图.jpg

1.为什么Objective-C是动态编程语言,因为它具有动态编程语言的特性维基百科:

动态编程语言是一类高级编程语言,它们在运行时执行静态编程语言在编译过程中执行的许多常见编程行为。这些行为可能包括程序的扩展,添加新的代码,扩展对象和定义或修改类型系统。

  1. 什么是runtime,先区分以下几个概念

Runtime 运行时

指的是程序跑起来的阶段,与之对应的是编译时

Runtime system 运行时系统

大多数编程语言都具有某种形式的运行时系统,该运行时系统提供了程序在其中运行的环境。此环境可能会解决许多问题,包括应用程序内存的管理,程序如何访问变量之间传递参数的机制,与操作系统的接口等等。通常,运行时系统将负责设置和管理堆栈和堆,并且可能包括以下功能:语言内置的垃圾回收,线程(计算)")或其他动态功能。

Runtime library运行时库

运行时库是一组地层程序,供编译器用来通过将对运行时库的调用插入已编译的可执行二进制文件中来调用运行时环境的某些行为。例如,某些只能在运​​行时执行(或更高效或更准确)的语言功能在运行时环境中实现,并且可以通过运行时库API调用,例如某些逻辑错误,数组边界检查,动态类型检查,异常处理以及调试功能。因此,尽管在开发过程中进行了复杂的编译时检查和测试,但是直到在具有真实数据的“实时”环境中测试程序后,才发现一些编程错误。

3.对这些概念了解之后,再来说说OC 的runtime

苹果官方解释

Objective-C Runtime 是一个运行时库,它提供对Objective-C语言的动态属性的支持,因此,所有Objective-C应用程序都将其链接到该库。在/usr/lib/libobjc.A.dylib共享库中实现了Objective-C运行时库支持功能。
该API主要用于开发Objective-C与其他语言之间的桥接层,或用于底层调试。

从编译时间和链接时间到运行时,Objective-C语言会尽可能多地推迟决策。只要有可能,它就会动态地执行操作。这意味着该语言不仅需要编译器,还需要运行时系统来执行编译后的代码。运行系统充当Objective-C语言的一种操作系统;这就是使语言有效的原因。

以上参考原文可以查看编程指南

可以理解为OC 的Runtime 是一个运行时库,用OC写的程序需要先编译成中间代码,在运行时通过运行时系统(runtime system)执行。(这个过程像java程序编译成字节码文件,再在JVM上运行)

OC的动态特性

runtime,的存在让OC具有动态语言的特性(虽然不是严格意义上的动态类型语言)
OC动态性体现在哪方面呢?

首先OC代码被Xcode编译后转换成了runtime代码RuntimeAPI文档,可以在终端通过clang -rewrite-objc [name].m转换,[name]是文件名

OC代码main.m

@interface Car : NSObject

-(void)run;

@end

@implementation Car

-(void)run{
    
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *car = [Car new];
        [car run];
    }
    return 0;
}

调用clang -rewrite-objc main.m生成main.cpp 打开后如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Car *car = ((Car *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Car"), sel_registerName("new"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)car, sel_registerName("run"));
    }
    return 0;
}

这并不是真正的编译过程,只是借助Clang生成C++文件去查看OC的底层代码,代码中出现了objc_msgSend这样的函数,这就是runtime库中的API, 而objc_msgSend就是给对象发送消息的API

动态性体现在三个方面

动态类型:

OC中所有的对象都被编译成id(NSObject *)类型,即使在编译是明确指定了类型 (NSString *)最终也会被转换成id,只有在运行时期这个id类型才会被识别为(NSString *)

动态绑定:

OC 中对象调用函数被称作发送消息,编译时
[car run];
编译后转换成runtime代码如下:
((void (*)(id, SEL))(void *)objc_msgSend)((id)car, sel_registerName("run"));
sel_registerName向runtime system注册一个方法名成功返回方法的SEL,如果已注册返回已有的SEL 此处就是“run”方法,因此编译的时候,即使没有方法实现,也能编译通过。等到运行时执行到这里,系统会根据 SEL 找到对应IMP,IMP是函数真正的实现地址,这就是动态绑定这样做的意义就是多态

动态加载:

根据需要动态的加载资源,典型的例子就是,iOS开发中所谓的@2x,@3x图系统会根据屏幕分辨率自动加载

OC的多态原理

OC是一门面相对象语言,所以符合 封装, 继承, 多态 三大特性,多态是最具代表性的;要想实现多态需要满足如下条件:

然后,上面转换后的代码简化一下就是:objc_msgSend(id,SEL), objc_msgSend这个方法内部做了以下操作:

objc_msgSend传入的两个参数,id,SEL
1.判断,id 是否为空,空的话直接返回nil就结束了,所以OC给nil发送消不会出问题,只是什么都不会发生
2.获取id对象的isa,(isa指向对象的类对象,此处就是Car类)
3.通过传入的SEL,从类对象的方法列表中找到对应的IMP(对象方法存储在类对象中)
4.找到IMP,调用真正的实现

以上过程只是简化版的,其中提到的isa,类对象 可以参考这篇OC 对象模型

消息发送
消息转发
动态方法解析

上一篇下一篇

猜你喜欢

热点阅读