OC种指针类型变量有两个:一个是编译时的类型,一个是运行时的类型;编译时的类型由声明该变量时使用的类型决定,运行时使用的类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现多态。
多态性
我们先来看一个程序
#import <Foundation/Foundation.h>
@interface Base : NSObject
- (void)base;
- (void)test;
@end
@implementation Base
- (void)base {
NSLog(@"父类的普通base方法");
}
- (void)test {
NSLog(@"父类的将被覆盖的test方法");
}
@end
@interface Subclass : Base
- (void)sub;
@end
@implementation Subclass
- (void)test {
NSLog(@"子类覆盖父类的test方法");
}
- (void)sub {
NSLog(@"子类的sub方法");
}
@end
int main() {
@autoreleasepool {
//bc指针编译和运行时类型相同,不存在多态
Base *bc = [[Base alloc] init];
[bc base];
[bc test];
//sc指针编译和运行时类型相同,不存在多态
Subclass *sc = [[Subclass alloc] init];
[sc base];
[sc test];
[sc sub];
//ploymophic_bc指针编译和运行时类型不同,发生多态
Base *ploymophic_bc = [[Subclass alloc] init];
//下面调用将执行从父类继承的base方法
[ploymophic_bc base];
//下面调用将执行子类重写的test方法
[ploymophic_bc test];
//因为ploymophic_bc的编译类型为Base
//Base类没有提供sub方法
//因此下面的代码编译时会报错
//[ploymophic_bc sub];
//可将任何类型的指针赋给id类型的变量
//此时调用的是该变量实际所指对象的方法
id dynamic = ploymophic_bc;
[dynamic sub];
}
}
输出:
该程序中main函数创建了4个指针类型的变量。对于前两个变量bc和sc,他们编译和运行时类型相同,因此调用方法正常。
而对于第三个指针变量ploymophic_bc来说,它编译时类型是Base,运行时类型是Subclass,当调用该指针的test方法时,此时调用的是该指针变量实际所指对象的方法,所以实际执行的是Subclass类中覆盖后的test方法,这就出现了多态。
对于第四个指针变量dynamic来说,它是id类型声明的变量,因此程序会自动追踪对象所属的类,并成功调用sub方法。因此我们说“通过id类型的变量来调用方法时,OC会执行动态绑定,即OC会追踪对象所属的类,在运行时判断对象所属的类、确定需要动态调用的方法,而不是在编译时确定”。
在OC中,子类是一种特殊的父类,因此允许把一个子类对象直接赋给一个父类指针变量,这被称为向上转型(upcasting),由系统自动完成。当完成该操作后,该变量编译时类型与运行时类型不一样,其方法总是表现出子类的方法特征,出现了相同类型的变量调用同一个方法时呈现出多种不同的行为特征,这就是多态。
指针变量的强制类型转换
编写OC程序时,除了id变量外,指针变量只能调用它编译时类型的方法。如果要让这个指针变量调用运行时类型的方法,必须强制转换。
OC为我们提供了类型转换运算符(),与基本数据类型的转换类似,其用法为 (type*)variable ,这种用法将variable转换成一个type类型的变量。
需要注意的是,这种强制转换要满足向上转型,类似于c语言中无法将字符串转换为整数形式,两者必须满足“父子关系”才能转换。
判断指针变量的实际类型
为保证程序正常运行,在强制转换之前要先判断能否强制转换(满足父子关系),OC为我们提供了两个方法:
- – (BOOL)isKindOfClass:clazz 判断该对象是否为clazz或其子类的实例
- – (BOOL)isSubclassOfClass:clazz 判断该对象是否为clazz子类的实例
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
//声明hello时使用的是NSObject类
//hello的编译类型是NSObject
//hello的实际行类型是NSString
NSObject *hello = @"hello";
NSLog(@"字符串是否为NSObject的实例:%d", ([hello isKindOfClass:[NSObject class]]));
NSLog(@"字符串是否为NSString的实例:%d", ([hello isKindOfClass:[NSString class]]));
NSLog(@"字符串是否为NSDate的实例:%d", ([hello isKindOfClass:[NSDate class]]));
NSString *a = @"hello";
NSLog(@"a是否为NSDate的实例:%d", ([a isKindOfClass:[NSDate class]]));
}
}
输出:
在实际程序中,使用该方法可以先判断前一个对象是否是该类的实例,是否可以成功转换,从而保证代码更加健壮。