【Runtime】动态添加方法demo

本文介绍如何利用Objective-C的runtime特性实现消息转发及动态添加方法,简化属性的get和set操作,提高代码可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天写一个小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.youkuaiyun.com/dolacmeng/article/details/53433623

完整代码下载:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值