众所周知,Objective-C 是一门动态语言。Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制,将很多静态语言在编译和连接(例如:C语言)时候的事放到了运行时来完成。
动态绑定
举个《Effective Objective-C 2.0》中的例子:
//静态绑定 (C语言)
#import
void printHello() {
printf("Hello world!");
}
void printGoodbye() {
printf("Goodbye world!");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
}else {
printGoodbye();
}
}
以上这段代码,编译器在编译代码的时候就会根据type的值来判断接生成调用eat()
或者run()
的指令。
如果将代码写成下面这样子:
//动态绑定
#import
void printHello() {
printf("Hello world!");
}
void printHello() {
printf("Goodbye world!");
}
void doTheThing(int type) {
void (*func)();
if (type == 0) {
func = printHello;
}else {
func = printGoodbye;
}
func();
}
这时候编译器在编译代码的时候只知道需要执行func()
,但是func()
具体是printHello()
还是printGoodbye()
是需要在运行时才能确定。
消息机制
在静态语言中,调用一个方法会直接跳转到该方法的实现部分执行,在编译时就决定好了。而在 Objective-C 中,在对象上调用方法,实际是发送了一条消息,消息有:名称(name)
或者选择子(selector)
,可以接受参数,也有可能有返回值。
在Objective-C 中, [object foo]
语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫foo
的消息。[object foo]
会被编译器翻译成一条标准的C语言函数调用:objc_msgSend(object, foo, ...)
。objc_msgSend
定义如下:
void objc_msgSend(id self,SEL op, ...)
第一个参数表示接收消息的对象,第二个参数即发送的消息,最后的...
表示可变参数,即消息中附带的参数,可以接收多个。
id resultValue = objc_msgSend(object, @selector)
下面两段代码是等价的:
NSString *string = @"Objective-C";
NSString *aString = [string stringByAppendingString:@" is fun"];
NSString *string = @"Objective-C";
NSString *aString = objc_msgSend(string, @selector(stringByAppendingString:),@" is fun");
objc_msgSend
函数会根据接收消息对象和selector
的类型来调用适当的方法。等等,什么叫适当的方法?Objective-C在编译的时候,会根据方法的名字,生成一个方法列表,里边存储用来区分方法的ID,这个ID是SEL类型的。SEL表示什么?SEL的定义:typedef struct objc_selector *SEL
,可以理解成方法的唯一标识。 @selector()
是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Objective-C的类不能直接应用函数指针,这样只能做一个@selector
语法来取,它的结果是一个SEL类型。这个类型本质是类方法的编号(函数地址)。如果objc_msgSend
找到了名称相符的方法,就跳转至实现代码,如果找不到,就沿着继承体系继续向上查找,如果直到最后都没有找到,就将消息转发。同时,objc_msgSend
会将匹配结果缓存到快速映射表里边,若是之后还有相同的消息,执行起来就很快了。
方法替换
利用Objective-C 的runtime特性,我们可以实现方法的替换:
void method_exchangeImplementations(Method m1, Method m2)
这个函数就是将两个方法进行替换的。m1
,m2
分别代表两个方法,而获取方法可以通过下面的函数:
Method class_getInstanceMethod(Class cls, SEL name)
接下来,把NSString
的lowercaseString
和 uppercaseString
方法替换,,看一下会发生什么:
Method lowercaseStringMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method uppercaseStringMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(lowercaseStringMethod, uppercaseStringMethod);
NSString *string = @"Objective-C";
NSLog(@"exchanged:%@",[string lowercaseString]);
// exchanged:OBJECTIVE-C
NSLog(@"exchanged:%@",[string uppercaseString]);
// exchanged:objective-c
结果是大小写方法被替换,调用lowercaseString
方法输出的是大写,调用uppercaseString
输出的是小写。
直接交换系统方法在开发中很少遇到,但是,可以通过方法交换为已经存在的方法修改或者增加功能。比方:我要在所有小写的字符串前面加上lower:
字符串,在所有大写字符串前加上UPPER:
字符串,利用给NSString
添加Catrgory
实现:
@interface NSString (MethodExchange)
- (NSString *)myLowercaseString;
- (NSString *)myUppercaseString;
@end
@implementation NSString (MethodExchange)
- (NSString *)myLowercaseString {
NSString *lowercase = [self myLowercaseString];
NSString *result = [@"lowwer:" stringByAppendingString:lowercase];
return result;
}
- (NSString *)myUppercaseString {
NSString *uppercase = [self myUppercaseString];
NSString *result = [@"UPPER:" stringByAppendingString:uppercase];
return result;
}
@end
如果仔细看的话,是不是会发现NSString *lowercase = [self myLowercaseString];
这一句好像是递归死循环。如果现在什么都不做,直接调用这个方法,确实会。但是我们接下来还要把方法替换,把myLowercaseString
和系统的lowercaseString
用method_exchangeImplementations
,其实相当于把两个方法大括号内部的实现代码替换掉,替换完其实是这样的:
- (NSString *)lowercaseString {
NSString *lowercase = [self myLowercaseString];
NSString *result = [@"lowwer:" stringByAppendingString:lowercase];
return result;
}
这样就不再是死循环了。
方法替换代码如下:
Method originLowerMthod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedLowerMthod = class_getInstanceMethod([NSString class], @selector(myLowercaseString));
method_exchangeImplementations(originLowerMthod, swappedLowerMthod);
NSLog(@"%@",[string lowercaseString]);
//lowwer:objective-c
Method originUpperMthod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
Method swappedUpperMthod = class_getInstanceMethod([NSString class], @selector(myUppercaseString));
method_exchangeImplementations(originUpperMthod, swappedUpperMthod);
NSLog(@"%@",[string uppercaseString]);
//UPPER:OBJECTIVE-C
通过这样,我们可以通过runtime特性,对完全不知道具体实现的方法添加新功能或者修改原有功能。
NEXT: 消息转发机制。