今天写一个小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个有效方法》
原创文章,转载请标注原文地址: http://blog.csdn.net/dolacmeng/article/details/53433623 完整代码下载: github下载: https://github.com/dolacmeng/RunTimeDemo csdn下载:http://download.csdn.net/detail/dolacmeng/9699784