笔记-OC语言的编译时与运行时

一直没有弄明白编译时和运行时的计算机都做了什么,这里研究了一下,参考了厚厚的iOS沉思录
为了加深印象,自己再写一遍。。。

编译时与运行时

编译时: 即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
运行时: 即程序通过了编译后,编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码简单扫描分析,此时若出错,程序会崩溃。

可以说编译时是一个静态的阶段,类型错误很明显可以直接检查出来,可读性也好;而运行时则是动态的阶段,开始具体与运行环境结合起来

OC语言的动态性

  • 动态类型
  • 动态绑定
  • 动态加载

动态类型

动态类型指对象指针类型的动态性,具体是指使用id类型将对象的类型确定推迟到运行时,由赋给它的对象类型决定对象指针的类型。另外类型确定推迟到运行时之后,可以通过NSObjectisKindOfClass方法动态判断对象最后的类型(动态类型识别)。也就是说id修饰的对象为动态类型对象,其他在编译器指明类型的为静态类型对象,通常如果不需要涉及到多态的话,还是要尽量使用静态类型(原因上面已经说到:错误可以在编译器提前查出,可读性好)。 示例:

对于语句 NSString *testObject = [[NSData alloc]init]; testObject在编译时和运行时分别是什么类型的对象?

首先testObject是一个指向某个对象的指针,不论何时指针的空间大小是固定的。

编译时:指针的类型为NSString,即编译时会被当做一个NSString实例来处理,编译器在类型检查的时候如果发现类型不匹配则会给出黄色警告,该语句给指针赋值用的是一个NSData对象,则编译时编译器则会给出类型不匹配警告。但编译时,如果testObject调用NSString的方法编译器会认为是正确的,既不会警告也不会报错。

运行时:运行时指针指向的实际是一个NSData对象,因此如果指针调用NSString的方法,虽然编译时通过了,但运行时会崩溃,因为NSData对象没有该方法;另外,虽然运行时指针实际指向的是NSData,但编译时编译器并不知道(前面说了编译器会把指针当成NSString对象处理),因此如果试图用这个指针调用NSData的方法会直接编译不通过,给出红色报错,程序也运行不起来。

下面给出测试例子:

    // 1.编译时编译器认为testObject是一个NSString对象,这里赋给它一个NSData对象编译器给出黄色类型错误警告,但运行时却指向一个NSData对象
    NSString *testObject = [[NSData alloc]init];
    // 2.编译器认为testObject是NSString对象,所以允许其调用NSString的方法,这里编译通过无警告和错误
    [testObject stringByAppendingString:@"string"];
    // 3.但不允许其调用NSData的方法,下面这里编译不通过给出红色报错
    [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
复制代码

将上面第三局编译不通过的注释掉,然后第二句打断点,编译后让程序跑起来到断点处会看到testObject指针的类型是_NSZeroData,指向一个NSData对象。继续运行程序会崩溃,因为NSData对象没有NSString的stringByAppendingString这个方法。

那么,假设testObject是 id类型会怎么样呢?

 // 1.id任意类型,编译器就不会把testObject在当成NSString对象了 
 id testObject = [[NSData alloc] init]; 
 // 2.调用NSData的方法编译通过 
 [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength]; 
 // 3.调用NSString的方法编译也通过 
 [testObject stringByAppendingString:@"string"];
复制代码

结果是编译完全通过,编译时编译器把testObject指针当成任意类型,运行时才确定testObject为NSData对象(断点看指针的类型和上面的例子中结果一样还是_NSZeroData,指向一个NSData对象),因此执行NSData的函数正常,但执行NSString的方法时还是崩溃了。通过这个例子也可以很清楚的知道id类型的作用了,将类型的确定延迟到了运行时,体现了OC语言的一种动态性:动态类型。

动态类型识别方法(面向对象语言的内省Introspection特性(没有太明白这句话意思。。。))

  • 首先是Class类型:
    • Class class = [NSObject class]; // 通过类名得到对应的Class动态类型
    • Class class = [obj class]; // 通过实例对象得到对应的Class动态类型
    • if([obj1 class] == [obj2 class]) // 判断是不是相同类型的实例
  • Class动态类型和类名字符串的相互转换:
    • NSClassFromString(@"NSObject"); // 由类名字符串得到Class动态类型
    • NSStringFromClass([NSObject class]); // 由类名的动态类型得到类名字符串
    • NSStringFromClass([obj class]); // 由对象的动态类型得到类名字符串
  • 判断对象是否属于某种动态类型:
    • -(BOOL)isKindOfClass:class // 判断某个对象是否是动态类型class的实例或其子类的实例
    • -(BOOL)isMemberOfClass:class // 与isKindOfClass不同的是,这里只判断某个对象是否是class类型的实例,不放宽到其子类
  • 判断类中是否有对应的方法:
    • -(BOOL)respondsToSelector:(SEL)selector // 类中是否有这个类方法
    • -(BOOL)instancesRespondToSelector:(SEL)selector // 类中是否有这个实例方法

区别:
上面两个方法都可以通过类名调用,前者判断类中是否有对应的类方法,后者判断类中是否有对应的实例方法,此外,前者respondsToSelector函数还可以被类的实例对象调用,效果等同于直接用类名调用后者instancesRespondToSelector函数。

  • 方法名字符串和SEL类型的转换:
    在编译期,编译器会根据方法的名字和参数序列生成唯一标识方法的ID,这个ID为SEL类型。到了运行时编译器通过SEL类型的ID来查找对应的方法,方法的名字和参数序列相同,那么他们的ID就是相同的。另外,可以通过@select()指示符获得方法的ID。
SEL funcID = @select(func); // 这个注册事件回调时常用,将方法转成SEL类型
SEL funcID = NSSelectorFromString(@"func"); // 根据方法名得到方法标识
NSString *funcName = NSStringFromSelector(funcID); // 根据SEL类型得到方法名字符串
复制代码

动态绑定

动态绑定指的是方法确定的动态性,具体指的是利用OC的消息传递机制将要执行的方法的确定推迟到运行时,可以动态添加方法。也就是说,一个OC对象是否调用某个方法不是由编译器决定的,而是由运行时决定的;另外关于动态绑定的关键一点是基于消息传递机制的消息转发机制,主要处理应对一些接收者无法处理的消息,此时有机会将消息转发给其他接收者处理。

动态绑定是基于动态类型的,在运行时对象的类型确定后,那么对象的属性和方法也就确定了(包括类中原来的属性和方法以及运行时动态新加入的属性和方法),这就是所谓的动态绑定了。动态绑定的核心就是在运行时动态的为类添加属性和方法,以及方法的最后处理或转发,主要用到C语言语法,因为涉及到运行时,因此要引入运行时头文件:#import<objc/runtime.h>

消息传递机制:
在OC中,方法的调用不再理解为对象调用其方法,而是要理解成对象接收消息,消息的发送采用‘动态绑定’机制,具体会调用哪个方法直到运行时才能确定,确定后才会执行绑定的代码。方法的调用实际就是告诉对象要干什么,给对象(的指针)传送一个消息,对象为接收者(receiver),调用的方法及其参数即消息(message),给一个对象传达消息表达式为:[receiver message]; 接收者的类型可以通过动态类型识别于运行时确定。

在消息传递机制中,当开发者编写 [receiver message];语句发送消息后,编译器都会将其转换成对应的一条objc_msgSendC语言消息发送原语,具体格式为:void objc_msgSend(id self, SEL cmd, ...)
这个原语函数参数可变,第一个参数填入消息的接收者,第二个参数是消息‘选择子’,后面跟着可选的消息的参数。有了这些参数,objc_msgSend就可以通过接收者的isa指针,到其类对象中的方法列表中以选择子的名称为键,寻找对应的方法,找到则转到其实现代码执行,找不到则继续根据继承关系从父类中寻找,如果到了根类还是无法找到对应的方法,说明该接收者对象无法响应该消息,则会触发‘消息转发机制’,给开发者最后一次挽救程序崩溃的机会。

动态加载

动态加载主要包括两个方面,一个事动态资源加载,一个是一些可执行代码模块的加载,这些资源在运行时根据需要动态的选择性的加入到程序中,是一种代码和资源的‘懒加载’模式,可以降低内存需求,提高整个程序的性能,另外也大大提高了可扩展性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值