利用runtime为setter方法添加功能

利用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地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容