最近比較清閑,就把以前學(xué)習(xí)的過程記錄下吧,多少年后如果能在互聯(lián)網(wǎng)上找到自己的痕跡,想想還是一件蠻值得高興的事情
涉及到的知識點
@dynamic
objc/runtime.h
@dynamic
說起dynamic 就要提跟它相關(guān)的@property 和 @synthesize
@property 是iOS 6以后出現(xiàn)的,使用它生成的變量,編譯器會自動幫我們生成setter 和 getter方法,還有一個系統(tǒng)默認(rèn)生成的以 _開頭的變量,它相當(dāng)于在.m中幫我們自動@synthesize varName = _varName 。@dynamic 就是告訴編譯器,這兩個方法我自己去實現(xiàn),讓編譯器去先通過編譯,但是如果運行的時候不去動態(tài)綁定它的setter 和getter方法,而去調(diào)用它的話,程序就會崩潰.
NSUserDefaults
我相信項目中大家都有用到NSUserDefaults ,而且非常普遍,用起來也順手。最常用的就是我們用它來保存用戶的登錄狀態(tài),然后一些登錄之后才能做的操作,就從本地去取這個狀態(tài)的值,從而去判斷用戶是否登錄
比方說有一個需求是保存用戶的手機(jī)號碼,下次進(jìn)入登錄頁面的時候,免除用戶填寫手機(jī)號碼的操作
[[NSUserDefaults standardUserDefaults] setObject:@"18888888888" forKey:@"phoneAccount"];
[[NSUserDefaults standardUserDefaults] synchronize];
其實代碼很簡單,但是每一次都要去寫兩行代碼才能實現(xiàn)這個操作。當(dāng)然我們可以把它寫成宏,比方說這樣
#define SET_OBJECT(object, key) \
({ \
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; \
[defaults setObject:object forKey:key]; \
[defaults synchronize]; \
})
這樣的話,又簡單了一些。但是往往我們的項目里面并不只是登錄這塊需要用到NSUserDefaults ,需要記錄的數(shù)據(jù)不止這些。那么我們想把代碼寫的更規(guī)范些,更清晰,更具可讀性的話,我們最好還是建立一個.h和.m文件專門記錄我們所需要存儲數(shù)據(jù)的key值。
當(dāng)然你可以只建立一個.h文件 使用宏去記錄,但是蘋果推薦我們使用這種方式去記錄一個常量
有了上面這些操作是不是就變的很方便了,確實方便。但是有一個第三方的輪子,能讓我們更方便的去替代上面這些操作
GVUserDefaults GitHub
到現(xiàn)在為止已經(jīng)800多star了,還是蠻好用的。直接看源碼,它的源碼只有一個.h 和一個.m文件。.h中只有一個創(chuàng)建單例的類方法
+ (instancetype)standardUserDefaults;
我們一步一步看,點進(jìn)去看它的init方法
- (instancetype)init {
self = [super init];
if (self) {
// 獲取一個SEL 類型的 選擇器
SEL setupDefaultSEL = NSSelectorFromString([NSString stringWithFormat:@"%@pDefaults", @"setu"]);
// 這個if 里面的操作后面會說到
if ([self respondsToSelector:setupDefaultSEL]) {
NSDictionary *defaults = [self performSelector:setupDefaultSEL];
NSMutableDictionary *mutableDefaults = [NSMutableDictionary dictionaryWithCapacity:[defaults count]];
for (NSString *key in defaults) {
id value = [defaults objectForKey:key];
NSString *transformedKey = [self _transformKey:key];
[mutableDefaults setObject:value forKey:transformedKey];
}
// 相當(dāng)于把mutableDefaults 里面的數(shù)據(jù) 配置為本地的默認(rèn)值
[self.userDefaults registerDefaults:mutableDefaults];
}
[self generateAccessorMethods];
}
return self;
}
我們再點進(jìn)去 方法 generateAccessorMethods ,核心代碼也都在這里
- (void)generateAccessorMethods {
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
self.mapping = [NSMutableDictionary dictionary];
for (int i = 0; i < count; ++i) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
const char *attributes = property_getAttributes(property);
char *getter = strstr(attributes, ",G");
if (getter) {
getter = strdup(getter + 2);
getter = strsep(&getter, ",");
} else {
getter = strdup(name);
}
SEL getterSel = sel_registerName(getter);
free(getter);
char *setter = strstr(attributes, ",S");
if (setter) {
setter = strdup(setter + 2);
setter = strsep(&setter, ",");
} else {
asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1);
}
SEL setterSel = sel_registerName(setter);
free(setter);
NSString *key = [self defaultsKeyForPropertyNamed:name];
[self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
[self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];
IMP getterImp = NULL;
IMP setterImp = NULL;
char type = attributes[1];
switch (type) {
case Short:
case Long:
case LongLong:
case UnsignedChar:
case UnsignedShort:
case UnsignedInt:
case UnsignedLong:
case UnsignedLongLong:
getterImp = (IMP)longLongGetter;
setterImp = (IMP)longLongSetter;
break;
case Bool:
case Char:
getterImp = (IMP)boolGetter;
setterImp = (IMP)boolSetter;
break;
case Int:
getterImp = (IMP)integerGetter;
setterImp = (IMP)integerSetter;
break;
case Float:
getterImp = (IMP)floatGetter;
setterImp = (IMP)floatSetter;
break;
case Double:
getterImp = (IMP)doubleGetter;
setterImp = (IMP)doubleSetter;
break;
case Object:
getterImp = (IMP)objectGetter;
setterImp = (IMP)objectSetter;
break;
default:
free(properties);
[NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self];
break;
}
char types[5];
snprintf(types, 4, "%c@:", type);
class_addMethod([self class], getterSel, getterImp, types);
snprintf(types, 5, "v@:%c", type);
class_addMethod([self class], setterSel, setterImp, types);
}
free(properties);
}
我們都知道OC是一門動態(tài)語言,嚴(yán)重依賴于runtime庫。
類Class 在runtime庫中由結(jié)構(gòu)體組成,它在runtime中的結(jié)構(gòu)體我們也不會陌生.
typedef struct objc_class *Class;
/*
這是由編譯器為每個類產(chǎn)生的數(shù)據(jù)結(jié)構(gòu),這個結(jié)構(gòu)定義了一個類.這個結(jié)構(gòu)是通過編譯器在執(zhí)行時產(chǎn)生,在運行時發(fā)送消息時使用.因此,一些成員改變了類型.編譯器產(chǎn)生"char* const"類型的字符串指針替代了下面的成員變量"super_class"
*/
struct objc_class {
struct objc_class* class_pointer; /* 指向元類的指針. */
struct objc_class* super_class; /* 指向父類的指針. 對于NSObject來說是NULL.*/
const char* name; /* 類的名稱. */
long version; /* 未知. */
unsigned long info; /* 比特蒙板. 參考下面類的蒙板定義. */
long instance_size; /* 類的字節(jié)數(shù).包含類的定義和所有父類的定義 */
#ifdef _WIN64
long pad;
#endif
struct objc_ivar_list* ivars; /* 指向類中定義的實例變量的列表結(jié)構(gòu). NULL代表沒有實例變量.不包括父類的變量. */
struct objc_method_list* methods; /* 鏈接類中定義的實例方法. */
struct sarray * dtable; /* 指向?qū)嵗椒ǚ峙浔? */
struct objc_class* subclass_list; /* 父類列表 */
struct objc_class* sibling_class;
struct objc_protocol_list *protocols; /* 要實現(xiàn)的原型列表 */
void* gc_object_type;
};
先看第一個方法
objc_property_t *properties = class_copyPropertyList([self class], &count);
獲取當(dāng)前Class的所有屬性
然后for循環(huán) 獲取每一個屬性的name 和 屬性特性描述字符串
const char *attributes = property_getAttributes(property);
我們也可以利用函數(shù)
unsigned int attrCount = 0;
objc_property_attribute_t * attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int j = 0; j < attrCount; j ++) {
objc_property_attribute_t attr = attrs[j];
const char * name = attr.name;
const char * value = attr.value;
}
free(attrs);
獲取變量的屬性的特性,objc_property_attribute_t結(jié)構(gòu)體包含name和value 常用的屬性:
屬性類型 name值:T value:變化
編碼類型 name值:C(copy) &(strong) W(weak) 空(assign) G(getter=method)S(setter=method)D(dynamic)等 value:無
非/原子性 name值:空(atomic) N(Nonatomic) value:無
變量名稱 name值:V value:變化
比方說有一個屬性 @property (nonatomic, copy) NSString *teacher;
那么使用property_copyAttributeList 獲取到的objc_property_attribute_t結(jié)構(gòu)體列表中的描述就是 T@"NSString",C,N,V_teacher
接著往下看,源碼中這一段
char *getter = strstr(attributes, ",G");
if (getter) {
getter = strdup(getter + 2);
getter = strsep(&getter, ",");
} else {
getter = strdup(name);
}
SEL getterSel = sel_registerName(getter);
free(getter);
char *getter = strstr(attributes, ",G"); 這個方法是什么意思呢?
從代碼上來看 應(yīng)該是用來判斷它的getter方法存不存在
我們可以建立一個測試demo來測試一下,還是teacher來舉例
我在if 和 else 里面分別打上斷點,并且運行
發(fā)現(xiàn)它并沒有走到if 里面去 ,而且getter為null 根據(jù)我們對@property 所知道的,編譯器會幫助我們自動生成setter方法 和 getter 方法啊,按理說getter應(yīng)該是有值的啊。再接著往下看,如果我在聲明屬性的時候這樣來寫
然后再運行來看
發(fā)現(xiàn)它就走到if 里面去了,由此來看 char *getter = strstr(propertyAttr, ",G"); 這個函數(shù)就用來判斷通過描述字符串獲取的objc_property_attribute_t()結(jié)構(gòu)體里面有沒有"G"的屬性,如果有就說明指定了getter方法,就由此描述字符串生成getter的SEL 如果沒有就用變量名稱生成
同樣的setter方法,跟getter方法差不多。緊接著這兩句方法
NSString *key = [self defaultsKeyForPropertyNamed:name];
[self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
[self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];我們點進(jìn)去看下
- (NSString *)defaultsKeyForPropertyNamed:(char const *)propertyName {
NSString *key = [NSString stringWithFormat:@"%s", propertyName];
return [self _transformKey:key];
}
- (NSString *)_transformKey:(NSString *)key {
if ([self respondsToSelector:@selector(transformKey:)]) {
return [self performSelector:@selector(transformKey:) withObject:key];
}
return key;
}
以變量名為key值 ,SEL 對應(yīng)的字符串為value 保存在mapping 里面。
下面的
IMP getterImp = NULL;
IMP setterImp = NULL;
char type = attributes[1];
switch (type) {
case Short:
case Long:
case LongLong:
case UnsignedChar:
case UnsignedShort:
case UnsignedInt:
case UnsignedLong:
case UnsignedLongLong:
getterImp = (IMP)longLongGetter;
setterImp = (IMP)longLongSetter;
break;
這里的代碼都是取描述字符串里第二個字節(jié)字符串去判斷變量所屬類型,分別生成setter和getter的IMP地址.
static long long longLongGetter(GVUserDefaults *self, SEL _cmd) {
NSString *key = [self defaultsKeyForSelector:_cmd];
return [[self.userDefaults objectForKey:key] longLongValue];
}
static void longLongSetter(GVUserDefaults *self, SEL _cmd, long long value) {
NSString *key = [self defaultsKeyForSelector:_cmd];
NSNumber *object = [NSNumber numberWithLongLong:value];
[self.userDefaults setObject:object forKey:key];
}
最后就是動態(tài)添加setter方法 和 getter方法
class_addMethod([self class], getterSel, getterImp, types);
class_addMethod([self class], setterSel, setterImp, types);
我是這樣使用他們
創(chuàng)建GVUserDefaults 的分類,.h文件
#define UserInfo [GVUserDefaults standardUserDefaults]
@property(nonatomic,weak)NSString* nickName;
@property(nonatomic,weak)NSString* account;
@property(nonatomic,weak)NSString* password;
// 恢復(fù)到初始狀態(tài)
- (void) restore;
.m文件
@dynamic nickName;
@dynamic password;
@dynamic account;
- (void)restore{
[self setValuesForKeysWithDictionary:[self setupDefaults]];
}
// 這個方法就是在init方法里面if 里面設(shè)置默認(rèn)數(shù)據(jù)的方法
- (NSDictionary *)setupDefaults {
static NSDictionary* dic = nil;
if (dic) {
return dic;
}else{
dic = @{
@"nickName":@"",
@"password":@"",
@"account":@"",
};
return dic;
}
}
這樣的話如果我們想使用UserDefaults 存儲一個值,就變的非常簡單,只需要在需要存儲的地方寫上 UserInfo.變量名 = 想要存儲的值就可以了。
當(dāng)然github上GVUserDefaults 還提供了更改key值的方法,這里我就不寫了,大家可以自己學(xué)習(xí)下。
總的來說,就是利用runtime庫去動態(tài)添加setter和getter方法。runtime其實在我們的平常工作中用處非常多,動態(tài)添加方法,交換方法,包括kvo的實現(xiàn)原理也是去動態(tài)監(jiān)聽setter和getter方法。
學(xué)無止境啊,讓我們一起努力吧。。。。