梦想者实用iOS 开发进阶干货

利用runtime为setter方法添加功能

2016-01-05  本文已影响689人  莫道别离伤

利用runtime为setter方法添加存储到本地功能

近来换了一家离家很近的公司工作,接手了一个老项目,独立进行二次开发。

项目中存在许多用户信息,且时常需要更新存储在本地,方便二次访问。

我的上一任是在每一次对其赋值后,使用userdefaults进行存储,没有封装,没有重写setter,直接在后面写上[NSUserD....],典型copy党···

我感觉我的膝盖中了一箭。

问题:

如何将已成型的类的属性更方便快捷的存储到本地?

解决方案分析:

1.重写setter方法,在每一个方法中都存储到本地:
- (void)setName:(NSString *)name
{
  _name = name;
  [[NSUserDefaults standardUserDefaults] setObject:name forKey:@"name"];
  [[NSUserDefaults standardUserDefaults] synchronize];
}

工作量大,代码冗余度高。

2.写一个方法对用户数据类进行统一存储到本地操作
- (void)savaUserData
{
  [[NSUserDefaults standardUserDefaults] setObject:_name forKey:@"name"];
  [[NSUserDefaults standardUserDefaults] setObject:_password forKey:@"password"];
 ......
 ......
  [[NSUserDefaults standardUserDefaults] synchronize];
}

工作量小,但只更改一个属性也需要进行整体存储,效率低。

3. 无视之~~
    是虽然不是处女~~座,但是这尼玛能忍!!?
4.运用运行时直接修改其setter,为其添加存储本地功能
    可以试试~~

懒,又追求效率,SO选择了方案4! 果然懒才是程序猿的第一生产力啊。`

实践

既然方案选择好了,Just do it。

步骤1: 书写通用new_setter方法

setter方法的本质是用属性的新值去替换掉旧值。

setter方法在C层面是一个带三个参数的函数

static void new_setter(id self, SEL _cmd, id newValue) OC类专用
static void new_setter(id self, SEL _cmd, long long newValue) 基本类型使用

      self是实例本身。
      _cmd是方法对应的SEL
      newValue顾名思义。

1.1 得到类型中对应属性的相关信息
由于实际项目中可以会不适用系统自动生成setter和getter方法自定义,则需要做一个通用的方法来获得对应的setter方法名m,在demo中我适用了一个类存储需要的相关信息,便于拓展

     objc_property_t * propertys = class_copyPropertyList(classs, &count);
      WKClassPropertyModel * model = [self new];
      model.name = [NSString  stringWithUTF8String:property_getName(property)];
      NSString * attrStr = [NSString stringWithFormat:@"%@",[NSString stringWithUTF8String:property_getAttributes(property)]];
      NSArray * attrs = [attrStr componentsSeparatedByString:@","];

for (NSString * str in attrs) {
    if([str hasPrefix:@"T"])//类型
    {
        model.type = [str substringFromIndex:1];
    }
    if([str hasPrefix:@"S"])//自定义setter
    {
        model.setterName = [str substringFromIndex:1];
    }
    if([str hasPrefix:@"G"])//自定义getter
    {
        model.getterName = [str substringFromIndex:1];
    }
    if([str hasPrefix:@"V"])//属性转换的变量名
    {
        model.varName = [str substringFromIndex:1];
    }
}

if (!model.setterName) {
    
    NSString * header =  [[model.name substringToIndex:1] uppercaseString];
    NSString * footer = [model.name substringFromIndex:1];
    model.setterName = [NSString stringWithFormat:@"set%@%@:",header,footer];
}

if (!model.getterName) {
    model.getterName = model.name;
}

return model;

1.2 遍历成员变量列表,替换成员变量值

    //得到变量列表
    Ivar * members = class_copyIvarList([self class], &count);

    int index = -1;
    //遍历变量
    for (int i = 0 ; i < count; i++) {
        Ivar var = members[i];
        //获得变量名
        const char *memberName = ivar_getName(var);

        //生成string
        NSString * memberNameStr = [NSString stringWithUTF8String:memberName];
        if ([varName isEqualToString:memberNameStr]) {
            index = i;
            break ;
        }
    
    }

    //变量存在则赋值
    if (index > -1) {
        Ivar member= members[index];
        object_setIvar(self, member, newValue);
    }

1.3 存储到本地——任意自由发挥阶段

    [[NSUserDefaults standardUserDefaults] setObject:newValue forKey:getterName];
    [[NSUserDefaults standardUserDefaults ]synchronize];
步骤2: 替换setter方法
unsigned int count = 0;

NSArray <WKClassPropertyModel *> * arr = [WKClassPropertyManager getClassPropertysWithClass:[self class]];
//获得方法列表
Method * a = class_copyMethodList([self class], &count);
//遍历方法列表
for (unsigned int i = 0; i < count; i ++) {
    
    NSString * methodName = NSStringFromSelector(method_getName(a[i]));
    
    for (WKClassPropertyModel * model in arr) {
        if ([model.setterName isEqualToString:methodName]) {
            if ([model.type containsString:@"@"])
            {
                method_setImplementation(a[i], (IMP)new_setter_object);
            }
            else
            {
                method_setImplementation(a[i], (IMP)new_setter_long);
            }
        }
    }

}

难点

1. setter方法如何通用
    
2. 在C层面如何替换方法

其实这两个问题都在于我对OC底层不熟悉导致。

OC的方法在底层是以method方法的形式存储在方法列表中,每一个方法实际对应一个IMP。
IMP实质就是一个函数指针。

SEL则类似方法名称,和实例以及IMP是一一对应关系。

一个实例不能有两个相同的SEL(方法名不能重复),一个SEL对应一个IMP。

所以我们可以通过SEL得到方法名称,进而找到成员变量名,完成setter方法的通用——解决难点1

同理由于method对应一个IMP,只需要将menthod的IMP更改为我们写的函数即可——解决难点2

附:Demo地址

上一篇下一篇

猜你喜欢

热点阅读