静态类型和动态类型
静态类型语言指的是在编译时检测类型的语言,也就是说,编程人员需要在代码中明确指出数据的类型,如C++,Java等。
动态类型语言和静态类型相对,指的是在运行时检测类型的语言,如Python等。
Objc属于混合型语言,C语言的部分是静态编程的,objc部分是动态编程的。只要发送的消息本身能够被对象识别,Objc的runtime不care对象的类型。
objc使用关键字id
来表示在编译阶段未指定的对象的类型。那么动态类型的优势在哪里呢?通过官方例子,可以管中窥豹:
NSArray *anArray = [NSArray arrayWithObjects:@"A string", [NSDecimalNumber zero], [NSDate date], nil];
NSInteger index;
for (index = 0; index < 3; index++) {
id anObject = [anArray objectAtIndex:index];
NSLog(@"Object at index %d is %@", index, [anObject description]);
}
从代码中可以看到,把array中的对象赋予动态类型,我们可以通过动态绑定调用各个对象中实现的方法。
objc的动态绑定:每个对象都有一个叫做
isa
的实例变量来制定对象所属的类。objc的runtime使用这个指针来决定对象的类。
类的动态类型
在objc中,类本身也被表示为对象,也就是说我们可以直接查询类的属性(introspection/自省方法),或者动态改变行为(反射)。这些强大的动态类型可以使得我们在不了解对象类型的情况下调用方法和属性。
// main.m
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *delorean = [[Car alloc] initWithModel:@"DeLorean"];
// Get the class of an object
NSLog(@"%@ is an instance of the %@ class",
[delorean model], [delorean class]);
// Check an object against a class and all subclasses
if ([delorean isKindOfClass:[NSObject class]]) {
NSLog(@"%@ is an instance of NSObject or one "
"of its subclasses",
[delorean model]);
} else {
NSLog(@"%@ is not an instance of NSObject or "
"one of its subclasses",
[delorean model]);
}
// Check an object against a class, but not its subclasses
if ([delorean isMemberOfClass:[NSObject class]]) {
NSLog(@"%@ is a instance of NSObject",
[delorean model]);
} else {
NSLog(@"%@ is not an instance of NSObject",
[delorean model]);
}
// Convert between strings and classes
if (NSClassFromString(@"Car") == [Car class]) {
NSLog(@"I can convert between strings and classes!");
}
}
return 0;
}
方法的动态绑定
performSelector:withObject
Selector
是objc对方法名称的内部表示,它使得方法和方法所属的对象分离开来。Ojbc中使用SEL
关键字来表示这种特殊的数据类型。
// main.m
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *porsche = [[Car alloc] init];
porsche.model = @"Porsche 911 Carrera";
SEL stepOne = NSSelectorFromString(@"startEngine");
SEL stepTwo = @selector(driveForDistance:);
SEL stepThree = @selector(turnByAngle:quickly:);
// This is the same as:
// [porsche startEngine];
[porsche performSelector:stepOne];
// This is the same as:
// [porsche driveForDistance:[NSNumber numberWithDouble:5.7]];
[porsche performSelector:stepTwo
withObject:[NSNumber numberWithDouble:5.7]];
if ([porsche respondsToSelector:stepThree]) {
// This is the same as:
// [porsche turnByAngle:[NSNumber numberWithDouble:90.0]
// quickly:[NSNumber numberWithBool:YES]];
[porsche performSelector:stepThree
withObject:[NSNumber numberWithDouble:90.0]
withObject:[NSNumber numberWithBool:YES]];
}
NSLog(@"Step one: %@", NSStringFromSelector(stepOne));
}
return 0;
}
可以看到,比较遗憾的是,这种方式最多只能调用最多含有两个参数、且没有返回值的函数。更复杂的函数的动态绑定需要使用NSInvocation实现。
NSInvocation
使用NSInvocation可以方便的封装含有任意数量参数的的函数。这篇文章做了很好的介绍,而且文末还有对NSInvocation的封装,非常给力。
Protocols - 协议
Protocols指的是一组可以被任意类实现的属性和方法。和interface相比,protocols具有更大的弹性,因为两个实现该协议的类可以不具有继承关系。
使用Protocol实现动态绑定
// main.m
#import <Foundation/Foundation.h>
#import "Bicycle.h"
#import "Car.h"
#import "StreetLegal.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
id <StreetLegal> mysteryVehicle = [[Car alloc] init];
[mysteryVehicle signalLeftTurn];
mysteryVehicle = [[Bicycle alloc] init];
[mysteryVehicle signalLeftTurn];
}
return 0;
}
无论Car和Bicycle是否具有一定的继承关系,他们都可以通过实现相同的protocol来进行动态绑定。
对象可以使用conformsToProtocol
方法来检查是否实现了某项协议。这个方法接收一个协议对象(通过@protocol
命令获取)作为参数。是不是想起了@seletor()
命令?
if ([mysteryVehicle conformsToProtocol:@protocol(StreetLegal)]) {
[mysteryVehicle signalStop];
[mysteryVehicle signalLeftTurn];
[mysteryVehicle signalRightTurn];
}
这个是很牛的动态类型工具,因为我们可以在对象中使用协议定义的非常好的API,而且不用在意这个对象到底是什么。