今天写一个小demo来演示下runtime的消息转发和动态添加方法。
一般项目中都会有保存当前登录用户资料的需求,我们可以直接将登录成功后的用户信息分别保存到NSUserDefaults中:
[def setObject:@"JackXu" forKey:@"UserName"];
[def setObject:@(YES) forKey:@"Sex"];
[def setObject:[[NSDate alloc] init] forKey:@“LoginDate"];
这种方法简直简单粗暴。老师常教导我们要面向对象编程,因此我们新建一个用户账户类:Passport,包含以下属性:用户名(NSString)、性别(Bool)、登录时间(NSDate),并实现获取单例的方法:sharedPassport。为了把数据保存到NSUserDefaults,并随时读取,我们需要重写每一个属性的set和get方法:
-(void)setName:(NSString *)name{
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
[def setObject:name forKey:@"UserName"];
}
-(NSString*)name{
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
return [def objectForKey:@"UserName"];
}
//其它属性的set和get方法
….
存取对象,我们只需要简单两句:
[[Passport sharedPassport] setName:@"Jack"];
NSString *name = [[Passport sharedPassport] name];
NSLog(@"name:%@",name);
上面代码保存了用户名Jack,然后再取出,打印到控制台。
现在看着比最初的方法好多了,但是如果项目后期需求说还要保存用户的头像地址、性别、电话、住址、会员到期日期等等等一大推用户信息呢?我们不仅要给Passport类添加这些属性,还得分别重写get和set方法。。。简直蒙蔽了。因此我们可以使用runtime动态添加方法,添加新的属性只需要在.h文件申明属性就行(*另外还要将属性声明为@dynamic,即不使用自动生成的get和set方法),get和set方法统统交给runtime完成吧!
在消息转发机制中,对象若收到无法解读的消息,首先会调用所属类的类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
参数selector就是未知的选择子(方法)。例如,将name声明为@dynamic后,执行[[Passport sharedPassport] setName:@“Jack”]时,会给Passport类发送一个setName:的消息,Passport收到消息后将其转换为函数调用,但是在Passport类中并没有setName:方法,因此会调用类的resolveInstanceMethod:方法,并将setName:作为参数传入。注意到resolveInstanceMethod:有Bool类型的返回值,表示这个类是否能新增一个实例方法来处理此选择子。因此我们要在resolveInstanceMethod:中完成未实现的set和get操作:
+ (BOOL)resolveInstanceMethod:(SEL)selector{
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self,
selector,
(IMP)autoDictionarySetter,
"v@:@");
}else{
class_addMethod(self,
selector,
(IMP)autoDictionaryGetter,
"@@:");
}
return YES;
}
上面的代码,先通过选择子的前缀判断是set方法还是get方法,然后通过class_addMethod()方法动态给Passport类添加对应的set或get方法。
下面将实现autoDictionarySetter和autoDictionaryGetter方法:
id autoDictionaryGetter(id self,SEL _cmd){
NSString *key = NSStringFromSelector(_cmd);
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
return [data objectForKey:key];
}
void autoDictionarySetter(id self, SEL _cmd, id value){
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];
NSUserDefaults *data = [NSUserDefaults standardUserDefaults];
if(value){
[data setObject:value forKey:key];
}else{
[data setObject:value forKey:key];
}
}
现在要给Passport增加一个昵称nickName的属性,只需要在.h中声明
@property(nonatomic,strong) NSString *nickName;
并在.m中添加@dynamic nickName即可,就可以直接读写用户的昵称:
[[Passport sharedPassport] setNickName:@"Jack"];
NSString *nickName = [[Passport sharedPassport] nickName];
NSLog(@“nickName:%@",nickName);
*如果为基本数据类型,要封装成NSNumber
*关于class_addMethod的最后一个参数,可以参考:http://blog.sina.com.cn/s/blog_6dce99b10101fhhp.html
*参考《编写高质量iOS与OS X代码的52个有效方法》