1.Objective-C对象
描述Objective-C对象所使用的数据结构定义在运行期程序库的头文件里,id类型本身也定义在这里:
typedef struct objc_object {
Class isa;
Class isa;
} *id;
id是指向objc_object结构体的指针。每个对象结构体的首个成员是Class类的变量。该变量定义了对象所属的类,通常称为“isa”指针。
Class对象也定义在运行期程序库的头文件中:
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
此结构体存放类的“元数据”,例如类的实例方法,实例变量等。此结构体的首个变量也是isa指针,这说明Class本身亦是Objective-C对象。结构体内还有个变量叫做super_class,它定义了本类的超类。类对象所属的类型(也就是isa指针所指向的的类型)是另外一个类,叫做“元类”,用来表述类对象本身所具有的元数据。“类方法”就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”。
假设有个名为SomeClass的子类从NSObject中继承而来,则其继承体系如图:
super_class指针确立了继承关系,而isa指针描述了实例所属的类。通过这张布局关系图即可执行“类型信息查询”。可以查出对象是否能响应某个选择子,是否遵从某项协议,并且能看出此对象位于“类继承体系”的哪一部分。
在类继承体系中查询类型信息
“isMemberOfClass:”能够判断出对象是否为某个特定类的实例,而“isKindOfClass:”则能判断对象是否为某类或其派生类的实例。例如
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]]; //NO
[dict isMemberOfClass:[NSMutableDictionary class]]; //YES
[dict isKindOfClass:[NSDictionary class]]; //YES
[dict isMemberOfClass:[NSDictionary class]]; //NO
[dict isMemberOfClass:[NSMutableDictionary class]]; //YES
[dict isKindOfClass:[NSDictionary class]]; //YES
[dict isKindOfClass:[NSArray class]]; //NO
像这样的类型信息查询方法使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走。
由于Objective使用“动态类型系统”,所以用于查询对象所属类的类型信息查询功能非常有用。从collection中获取对象时,通常会查询类型信息,这些对象不是“强类型的”,把它们从collection取出来时,其类型通常是id。如果想知道具体类型,就可以使用类型信息查询。
- (NSString*)commaSeparatedStringFromObject:(NSArray*)array {
NSMutableString *string = [NSMutableString new];
for (id object in array) {
if ([object isKindOfClass:[NSString class]]) {
[string appendFormat:@"%@,",object];
}else if([object isKindOfClass:[NSNumber class]]) {
[string appendFormat:@"%d," [object intValue]];
}
}
return string;
NSMutableString *string = [NSMutableString new];
for (id object in array) {
if ([object isKindOfClass:[NSString class]]) {
[string appendFormat:@"%@,",object];
}else if([object isKindOfClass:[NSNumber class]]) {
[string appendFormat:@"%d," [object intValue]];
}
}
return string;
}
2.消息发送
静态绑定:在编译期就能决定运行时所应调用的函数。
void printHello() {
printf("Hello, world\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
} else {
printGoodbye();
}
printf("Hello, world\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
if (type == 0) {
printHello();
} else {
printGoodbye();
}
}
编译器在编译代码的时候就已经知道程序中有printHello与printGoodbye这两个函数,于是直接生成调用这些函数的指令。而函数地址实际上硬编码在指令之中的。
动态绑定:调用的函数知道运行期才能确定。
void printHello() {
printf("Hello, world\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
} else {
fnc = printGoodbye;
}
fnc();
return 0;
printf("Hello, world\n");
}
void printGoodbye() {
printf("Goodbye, world!\n");
}
void doTheThing(int type) {
void (*fnc)();
if (type == 0) {
fnc = printHello;
} else {
fnc = printGoodbye;
}
fnc();
return 0;
}
只有一个函数调用指令,不过待调用的函数地址无法硬编码在指令之中,而是要在运行期读取出来。
Objective-C动态绑定
Objective-C动态绑定使用来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定。
给对象发送消息:
id returnValue = [someObject messageName:parameter];
someObject叫做“接收者”,messageName叫做“选择子”。选择子与参数合起来称为“消息”。编译器看到此消息后,将其转换为一条标准的C语言函数调用,叫做objc_msgSend,其原型如下:
void objc_msgSend(id self,SEL cmd, ...)
编译器把刚才那个例子中的消息转换为如下函数:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend函数会依据接收者与选择子的类型来调用适当的方法。为了完成此操作,该方法需要在接收者所属的类中搜寻其“方法列表”如果能找到与选择子名称相符的方法,就跳至其实现代码。若是找不到,就沿着继承体系继续向上查找,等找到合适的方法之后再跳转。如果最终还是找不到相符的方法,那就执行“消息转发”操作。
objc_msgSend会将匹配结果缓存在“快速映射表”里面,每个类都有一个这样的缓存,若是稍后还向该类发送与选择子相同的消息,执行起来就很快了。当然还是没有静态绑定快。
objc_msgSend一旦找到应该调用的方法实现之后,就会“跳转过去”。之所以能这样做,是因为Objective-C对象的每个方法都可以视为简单的C函数,其原型如下:
<return_type> Class_selector(id self, SEL _cmd, ...)
每个类里都有一张表格(方法列表),方法列表包含每个方法的键和指针,键是选择子的名称,指针都会指向这个函数。
3.消息转发机制
在编译期向类发送了其无法解读的消息并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译期时还无法确知类中到底会不会有某个方法实现。
当对象接收到无法解读的消息后,就会启动“消息转发”机制,程序员可经由此过程告诉对象应该如何处理未知消息。
消息转发分为两个阶段。第一阶段先征询接收者所属的类,看其是否动态添加方法,以处理当前“未知的选择子”,这叫做“动态方法解析”。如果没有动态添加方法,接收者会看看有没有其他对象能处理这条消息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束。若没有“备援的接收者”,则启动完整的消息转发机制。
动态方法解析
对象在收到无法解读的消息后,首先将调用其所属类的下列方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
该方法参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有机会新增一个处理选择子的方法。
使用下面方法向类动态添加方法:
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *type);
name为方法名,imp为方法对应的函数指针,将该方法添加到类cls上。
备援接收者
在这一步中,运行期系统会问它:能不能把这条消息转发给其他接收者来处理。对应的处理方法为:
- (id)forwardingTargetForSelector:(SEL)aSelector
若当前接收者能找到备援对象,则将其返回,若找不到,就返回nil。通过此方案,可以用“组合”来模拟“多重继承”的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回,在外界看来,好像是该对象亲自处理了这些消息似的。
完整的消息转发
首先创建NSInvocation对象,把与尚未处理的那条消息有关全部细节都封于其中。此对象包含选择子、目标及参数。在触发NSInvocation对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation
消息转发全流程(图)
步骤越往后,处理消息的代价越大。
完整的例子
演示动态方法解析:动态方法解析实现@dynamic属性
编写一个类似于“字典”的对象,它里面可以容纳其他对象,只不过开发者要直接通过属性来存取其中的数据。由开发者添加属性定义,并将其声明为@dynamic,而类则会自动处理相关属性值的存放与获取操作。
#import <Foundation/Foundation.h>
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) id opaqueObject;
@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) id opaqueObject;
@end
#import "EOCAutoDictionary.h"
#import <objc/runtime.h>
@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation EOCAutoDictionary
@dynamic string,number,data,opaqueObject;
- (id)init {
if (self = [super init]) {
_backingStore = [NSMutableDictionary new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *seletorString = NSStringFromSelector(sel);
#import <objc/runtime.h>
@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end
@implementation EOCAutoDictionary
@dynamic string,number,data,opaqueObject;
- (id)init {
if (self = [super init]) {
_backingStore = [NSMutableDictionary new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *seletorString = NSStringFromSelector(sel);
if ([seletorString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@“); //sel为选择子,autoDictionarySetter为函数指针,最后一个参数为所添加的方法的参数。
} else {
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
id autoDictionaryGetter(id self, SEL _cmd) { //函数,不是方法(前面没有-号)
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
NSMutableDictionary *backingStore = typedSelf.backingStore;
NSString *key = NSStringFromSelector(_cmd);
return [backingStore objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value) { //函数,不是方法(前面没有-号)
EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
NSMutableDictionary *backingStore = typedSelf.backingStore;
NSString *seletorString = NSStringFromSelector(_cmd);
NSMutableString *key = [seletorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];//删除:符号
[key deleteCharactersInRange:NSMakeRange(0, 3)];//删除set
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
} else {
[backingStore removeObjectForKey:key];
}
NSMutableDictionary *backingStore = typedSelf.backingStore;
NSString *seletorString = NSStringFromSelector(_cmd);
NSMutableString *key = [seletorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length-1, 1)];//删除:符号
[key deleteCharactersInRange:NSMakeRange(0, 3)];//删除set
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[backingStore setObject:value forKey:key];
} else {
[backingStore removeObjectForKey:key];
}
}
要点:
1.若对象无法响应某个选择子,则进入消息转发机制。
2.通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
3.对象可以把其无法解读的某些选择子转交给其他对象来处理。
4.经过上述两步之后,如果还是没有办法处理选择子,那就启动完整的消息转发机制。
4.方法调配技术
类方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态消息派发系统”能够据此应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做IMP,其原型:id (*IMP) (id, SEL, …)
NSString类可以响应lowercaseString、uppercaseString、capitalizedString等选择子,这张映射表中每个选择子都映射到了不同的IMP之上
开发者可以向其中新增选择子,也可以改变某选择子所对应的方法实现,还可以交换两个选择子所映射到的指针。
可以通过这一手段来为既有的方法实现增添新功能。比方说,想要在调用lowercaseString时记录某些信息,这时就可以通过交换方法来实现达成此目标。新编写一个方法,在此方法中实现所需的附加功能,并调用原有实现。
新方法可以添加至NSString的一个分类中
@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;
- (NSString*)eoc_myLowercaseString;
@end
@implementation NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString {
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
NSString *lowercase = [self eoc_myLowercaseString];
NSLog(@"%@ => %@", self, lowercase);
return lowercase;
}
@end
这段代码看上去好像会陷入递归调用的死循环,不过大家要记住,此方法是准备和lowercaseString方法互换的。所以在运行期,eoc_myLowercaseString选择子实际上对应原有的lowercaseString方法实现。
实现方法交换:
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
要点:
1.在运行期,可以向类中新增或替换选择子所对应的方法实现。
2.使用另一份实现来替换原有的方法实现,这道工序叫做”方法调配“,开发者常用此技术向原有实现添加功能。(交换选择子对应的函数指针)
3.一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。