深入剖析OC Runtime(三) Message Forwa
2017-07-25 本文已影响25人
ShawnDu
深入剖析OC Runtime(三) Message Forward Demo
原文地址
objc_msgSend
objc_msgSend的格式如下:
void objc_msgSend(id self, SEL cmd, parameter...)
OC中所有的调用方法,属性赋值,都会转化为上面的C函数发送消息。找不到方法时,会走消息转发机制。
消息转发
先上图, 消息转发全流程消息转发分为两大阶段。
- 第一阶段先问接收者,所属的类,看能否动态添加方法,以处理这个未知的selector,这叫动态方法解析。
- 第二阶段涉及完整的消息转发机制。
如果运行期系统已经把第一阶段执行完了,那接收者自己就无法再以动态新增方法的手段来响应包含该selector的消息了。此时运行期系统会请求接收者以其他手段来处理与消息相关的方法调用。这又分为两步:a.请接发者看看有没有其他对象能处理这条消息,若有,则runtime会把消息转给那个对象,转发过程结束。若没备用的接收者,会启动完整的消息转发机制,runtime会把与消息有关的全部细节都封装在NSInvocation对象中,再给接收者最后一次机会。
示例
由开发者来添加属性的定义,并声明为@dynamic,此类会自动处理属性值的存放。
header中:
#import <Foundation/Foundation.h>
@interface SSAutoDictionary : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSNumber *number;
@end
上面定义了几种数据类型。
在实现文件中:
#import "SSAutoDictionary.h"
#import <objc/runtime.h>
@interface SSAutoDictionary()
@property (nonatomic, strong) NSMutableDictionary *backStore;
@end
@implementation SSAutoDictionary
@dynamic name, date, number;
- (id)init {
if (self = [super init]) {
_backStore = [NSMutableDictionary new];
}
return self;
}
假如只这样写,从外部访问属性的set和get方法时,都会找不到方法,所以引入了resolveInstanceMethod:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
} else {
class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
}
return YES;
}
用前缀断定是否为set,两种情况下,都向类中新增一个处理该selector的子方法,这两个方法以函数指针形式出现autoDictionarySetter和autoDictionaryGetter。Getter方法:
id autoDictionaryGetter(id self, SEL _cmd) {
SSAutoDictionary *mSelf = (SSAutoDictionary *)self;
NSString *key = NSStringFromSelector(_cmd);
return [mSelf.backStore objectForKey:key];
}
setter方法:
void autoDictionarySetter(id self, SEL _cmd, id value) {
SSAutoDictionary *mSelf = (SSAutoDictionary *)self;
NSString *selectorString = NSStringFromSelector(_cmd);
NSMutableString *key = [selectorString mutableCopy];
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
[key deleteCharactersInRange:NSMakeRange(0, 3)];
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
if (value) {
[mSelf.backStore setObject:value forKey:key];
} else {
[mSelf.backStore removeObjectForKey:key];
}
}
使用的时候:
SSAutoDictionary *dict = [SSAutoDictionary new];
dict.date = [NSDate new];
NSLog(@"dict.date = %@", dict.date);
输出:
RuntimeDemo[61043:9339954] dict.date = 2017-07-25 13:19:22 +0000