编译时: 即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
运行时: 即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。
可以说编译时是一个静态的阶段,类型错误很明显可以直接检查出来,可读性也好;而运行时则是动态的阶段,开始具体与运行环境结合起来。
OC语言的动态性主要体现在三个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
第一部分:动态类型(Dynamic typing)
动态类型指的是对象指针类型的动态性,具体是指使用id任意类型将对象的类型确定推迟到运行时,由赋给它的对象类型决定对象指针的类型。另外类型确定推迟到运行时之后,可以通过NSObject的isKindOfClass方法动态判断对象最后的类型(动态类型识别)。也就是说id修饰的对象为动态类型对象,其他在编译器指明类型的为静态类型对象,通常如果不需要涉及到多态的话还是要尽量使用静态类型(原因上面已经说到:错误可以在编译器提前查出,可读性好)。
实例1 :
NSString* testObject = [[NSData alloc] init];
对于语句NSString* testObject = [[NSData alloc] init]; testObject在编译时和运行时分别是什么类型的对象?
首先testObject是一个指向某个对象的指针,不论何时指针的空间大小是固定的。
编译时: 指针的类型为NSString,即编译时会被当成一个NSString实例来处理,编译器在类型检查的时候如果发现类型不匹配则会给出黄色警告,该语句给指针赋值用的是一个NSData对象,则编译时编译器则会给出类型不匹配警告。但编译时如果testObject调用NSString的方法编译器会认为是正确的,既不会警告也不会报错。
运行时: 运行时指针指向的实际是一个NSData对象,因此如果指针调用了NSString的方法,虽然编译时通过了,但运行时会崩溃,因为NSData对象没有该方法;另外,虽然运行时指针实际指向的是NSData,但编译时编译器并不知道(前面说了编译器会把指针当成NSString对象处理),因此如果试图用这个指针调用NSData的方法会直接编译不通过,给出红色报错,程序也运行不起来.
实例2 :关于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特性)
1.首先是Class类型:
Class class = [NSObject class]; // 通过类名得到对应的Class动态类型
Class class = [obj class]; // 通过实例对象得到对应的Class动态类型
if([obj1 class] == [obj2 class]) // 判断是不是相同类型的实例
2.Class动态类型和类名字符串的相互转换:
NSClassFromString(@”NSObject”); // 由类名字符串得到Class动态类型
NSStringFromClass([NSObject class]); // 由类名的动态类型得到类名字符串
NSStringFromClass([obj class]); // 由对象的动态类型得到类名字符串
3.判断对象是否属于某种动态类型:
-(BOOL)isKindOfClass:class // 判断某个对象是否是动态类型class的实例或其子类的实例
-(BOOL)isMemberOfClass:class // 与isKindOfClass不同的是,这里只判断某个对象是否是class类型的实例,不放宽到其子类
4.判断类中是否有对应的方法:
-(BOOL)respondsTosSelector:(SEL)selector // 类中是否有这个类方法
-(BOOL)instancesRespondToSelector:(SEL)selector // 类中是否有这个实例方法
第二部分:动态绑定(Dynamic binding)
动态绑定指的是方法确定的动态性,具体指的是利用OC的消息传递机制将要执行的方法的确定推迟到运行时,可以动态添加方法。也就是说,一个OC对象是否调用某个方法不是由编译器决定的,而是由运行时决定的;另外关于动态绑定的关键一点是基于消息传递机制的消息转发机制,主要处理应对一些接受者无法处理的消息,此时有机会将消息转发给其他接收者处理,具体见下面介绍。
动态绑定是基于动态类型的,在运行时对象的类型确定后,那么对象的属性和方法也就确定了(包括类中原来的属性和方法以及运行时动态新加入的属性和方法),这也就是所谓的动态绑定了。动态绑定的核心就该是在运行时动态的为类添加属性和方法,以及方法的最后处理或转发,主要用到C语言语法,因为涉及到运行时,因此要引入运行时头文件:#include <objc/runtime.h>。
接下来时消息转发机制:
第三部分:动态加载(Dynamic loading)
动态加载主要包括两个方面,一个是动态资源加载,一个是一些可执行代码模块的加载,这些资源在运行时根据需要动态的选择性的加入到程序中,是一种代码和资源的‘懒加载’模式,可以降低内存需求,提高整个程序的性能,另外也大大提高了可扩展性。
例如:资源动态加载中的图片资源的屏幕适配,同一个图片对象可能需要准备几种不同分辨率的图片资源,程序会根据当前的机型动态选择加载对应分辨率的图片,像iphone4之前老机型使用的是@1x的原始图片,而retina显示屏出现之后每个像素点被分成了四个像素,因此同样尺寸的屏幕需要4倍分辨率(宽高各两倍)的@2x图片,最新的针对iphone6/6+以上的机型则需要@3x分辨率的图片。例如下面所示应用的AppIcon,需要根据机型以及机型分辨率动态的选择加载某张具体的图片资源。
涉及到的问题:
问题1:
我们所说的Objective-C是动态运行时语言是什么意思?
主要指的是OC语言的动态性,包括动态性和多态性两个方面。
动态性:即OC的动态类型、动态绑定和动态加载特性,将对象类型的确定、方法调用的确定、代码和资源的装载等推迟到运行时进行,更加灵活;
多态:多态是面向对象变成语言的特性,OC作为一门面向对象的语言,自然具备这种多态性,多态性指的是来自不同类的对象可以接受同一消息的能力,或者说不同对象以自己的方式响应相同的消息的能力。
问题:
动态绑定是在运行时确定要调用的方法?
动态绑定将调用方法的确定推迟到运行时。在编译时,方法的调用并不和代码绑定在一起,只有在消实发送出来之后,才确定被调用的代码。通过动态类型和动态绑定技术,代码每次执行都可以得到不同的结果。运行时负责确定消息的接收者和被调用的方法。运行时的消息分发机制为动态绑定提供支持。当向一个动态类型确定了的对象发送消息时,运行环境会通过接收者的isa指针定位对象的类,并以此确定被调用的方法,方法是动态绑定的。
问题3:
解释OC中的id类型?id、nil代表什么?
id表示变量或对象的类型在编写代码时(编译期)不确定,视为任意类型,直到程序跑起来推迟到运行时才最终确定其类型。id类似于C/C++中的void *,但id和void*并非完全一样。id是一个指向继承了NSObject的OC对象的指针,注意id是一个指针,虽然省略了*号。id和C语言的void*之间需要通过bridge关键字来显式的桥接转换。具体转换方式示例如下:
id nsobj = [[NSObject alloc] init];
void *p = (__bridge void *)nsobj;
id nsobj = (__bridge id)p;
OC中的nil定义在objc/objc.h中,表示的是一个指向空的Objctive-C对象的指针。例如weak修饰的弱引用对象在指向的对象释放时会自动将指针置为nil,即空对象指针,防止‘指针悬挂’。
问题4:
instancetype和id的区别?
instancetype和id都可以用来代表任意类型,将对象的类型确定往后推迟,用于体现OC语言的动态性,使其声明的对象具有运行时的特性。
它们的区别是:instancetype只能作为返回值类型,但在编译期instancetype会进行类型检测(即instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象)因此对于所有返回类的实例的类方法或实例方法,建议返回值类型全部使用instancetype而不是id,具体原因后面举例介绍;id类型既可以作为返回值类型,也可以作为参数类型,也可以作为变量的类型,但id类型在编译期不会进行类型检测。