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