Objective-C运行时

1.Objective-C对象

描述Objective-C对象所使用的数据结构定义在运行期程序库的头文件里,id类型本身也定义在这里:
typedef struct objc_object {
    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;
}
此结构体存放类的“元数据”,例如类的实例方法,实例变量等。此结构体的首个变量也是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 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;
}

2.消息发送

静态绑定:在编译期就能决定运行时所应调用的函数。
void printHello() {
   
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;
}
只有一个函数调用指令,不过待调用的函数地址无法硬编码在指令之中,而是要在运行期读取出来。

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;

@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);
    if ([seletorString hasPrefix:@"set"]) {
        class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@); //sel为选择子,autoDictionarySetter为函数指针,最后一个参数为所添加的方法的参数。
    } else {
       
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];
}
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];
    }
}

要点:

1.若对象无法响应某个选择子,则进入消息转发机制。
2.通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
3.对象可以把其无法解读的某些选择子转交给其他对象来处理。
4.经过上述两步之后,如果还是没有办法处理选择子,那就启动完整的消息转发机制。

4.方法调配技术

类方法列表会把选择子的名称映射到相关的方法实现之上,使得“动态消息派发系统”能够据此应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做IMP,其原型:id (*IMP) (id, SEL, …)

NSString类可以响应lowercaseString、uppercaseString、capitalizedString等选择子,这张映射表中每个选择子都映射到了不同的IMP之上


开发者可以向其中新增选择子,也可以改变某选择子所对应的方法实现,还可以交换两个选择子所映射到的指针。


可以通过这一手段来为既有的方法实现增添新功能。比方说,想要在调用lowercaseString时记录某些信息,这时就可以通过交换方法来实现达成此目标。新编写一个方法,在此方法中实现所需的附加功能,并调用原有实现。
新方法可以添加至NSString的一个分类中
@interface NSString (EOCMyAdditions)
- (
NSString*)eoc_myLowercaseString;
@end

@implementation NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString {
   
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.一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值