利用runtime為setter方法添加功能

利用runtime為setter方法添加存儲(chǔ)到本地功能

近來(lái)?yè)Q了一家離家很近的公司工作,接手了一個(gè)老項(xiàng)目,獨(dú)立進(jìn)行二次開(kāi)發(fā)。

項(xiàng)目中存在許多用戶信息,且時(shí)常需要更新存儲(chǔ)在本地,方便二次訪問(wèn)。

我的上一任是在每一次對(duì)其賦值后,使用userdefaults進(jìn)行存儲(chǔ),沒(méi)有封裝,沒(méi)有重寫(xiě)setter,直接在后面寫(xiě)上[NSUserD....],典型copy黨···

我感覺(jué)我的膝蓋中了一箭。

問(wèn)題:

如何將已成型的類(lèi)的屬性更方便快捷的存儲(chǔ)到本地?

解決方案分析:

1.重寫(xiě)setter方法,在每一個(gè)方法中都存儲(chǔ)到本地:
- (void)setName:(NSString *)name
{
  _name = name;
  [[NSUserDefaults standardUserDefaults] setObject:name forKey:@"name"];
  [[NSUserDefaults standardUserDefaults] synchronize];
}

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

2.寫(xiě)一個(gè)方法對(duì)用戶數(shù)據(jù)類(lèi)進(jìn)行統(tǒng)一存儲(chǔ)到本地操作
- (void)savaUserData
{
  [[NSUserDefaults standardUserDefaults] setObject:_name forKey:@"name"];
  [[NSUserDefaults standardUserDefaults] setObject:_password forKey:@"password"];
 ......
 ......
  [[NSUserDefaults standardUserDefaults] synchronize];
}

工作量小,但只更改一個(gè)屬性也需要進(jìn)行整體存儲(chǔ),效率低。

3. 無(wú)視之~~
    是雖然不是處女~~座,但是這尼瑪能忍!!?
4.運(yùn)用運(yùn)行時(shí)直接修改其setter,為其添加存儲(chǔ)本地功能
    可以試試~~

懶,又追求效率,SO選擇了方案4! 果然懶才是程序猿的第一生產(chǎn)力啊。`

實(shí)踐

既然方案選擇好了,Just do it。

步驟1: 書(shū)寫(xiě)通用new_setter方法

setter方法的本質(zhì)是用屬性的新值去替換掉舊值。

setter方法在C層面是一個(gè)帶三個(gè)參數(shù)的函數(shù)

static void new_setter(id self, SEL _cmd, id newValue) OC類(lèi)專(zhuān)用
static void new_setter(id self, SEL _cmd, long long newValue) 基本類(lèi)型使用

      self是實(shí)例本身。
      _cmd是方法對(duì)應(yīng)的SEL
      newValue顧名思義。

1.1 得到類(lèi)型中對(duì)應(yīng)屬性的相關(guān)信息
由于實(shí)際項(xiàng)目中可以會(huì)不適用系統(tǒng)自動(dòng)生成setter和getter方法自定義,則需要做一個(gè)通用的方法來(lái)獲得對(duì)應(yīng)的setter方法名m,在demo中我適用了一個(gè)類(lèi)存儲(chǔ)需要的相關(guān)信息,便于拓展

     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"])//類(lèi)型
    {
        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"])//屬性轉(zhuǎn)換的變量名
    {
        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 存儲(chǔ)到本地——任意自由發(fā)揮階段

    [[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);
            }
        }
    }

}

難點(diǎn)

1. setter方法如何通用
    
2. 在C層面如何替換方法

其實(shí)這兩個(gè)問(wèn)題都在于我對(duì)OC底層不熟悉導(dǎo)致。

OC的方法在底層是以method方法的形式存儲(chǔ)在方法列表中,每一個(gè)方法實(shí)際對(duì)應(yīng)一個(gè)IMP。
IMP實(shí)質(zhì)就是一個(gè)函數(shù)指針。

SEL則類(lèi)似方法名稱(chēng),和實(shí)例以及IMP是一一對(duì)應(yīng)關(guān)系。

一個(gè)實(shí)例不能有兩個(gè)相同的SEL(方法名不能重復(fù)),一個(gè)SEL對(duì)應(yīng)一個(gè)IMP。

所以我們可以通過(guò)SEL得到方法名稱(chēng),進(jìn)而找到成員變量名,完成setter方法的通用——解決難點(diǎn)1

同理由于method對(duì)應(yīng)一個(gè)IMP,只需要將menthod的IMP更改為我們寫(xiě)的函數(shù)即可——解決難點(diǎn)2

附:Demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容