GVUserDefaults源碼閱讀及使用

最近比較清閑,就把以前學(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值。

3CEDC9ED-3BA0-43E2-9E2C-26B9783C063A.png
3BAEFFF0-4D1E-4DD5-812E-50F398DE94BD.png

當(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 里面分別打上斷點,并且運行

55AE9275-97C2-4140-96EA-1AE0ADD78B92.png

![Uploading 55AE9275-97C2-4140-96EA-1AE0ADD78B92_103211.png . . .]

發(fā)現(xiàn)它并沒有走到if 里面去 ,而且getter為null 根據(jù)我們對@property 所知道的,編譯器會幫助我們自動生成setter方法 和 getter 方法啊,按理說getter應(yīng)該是有值的啊。再接著往下看,如果我在聲明屬性的時候這樣來寫


6E1752C7-483A-4FD4-9FCB-E3410BF5E3CB.png

然后再運行來看

12286FF4-3DAA-47AA-BAEC-32620E96DC84.png

發(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é)無止境啊,讓我們一起努力吧。。。。

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

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

  • 本文將討論Java設(shè)計模式中比較重要的模式之一:裝飾者模式 該系列其他文章: 安卓設(shè)計模式(一)面向?qū)ο罅笤O(shè)計原...
    uncochen閱讀 1,642評論 1 9
  • 01 中午吃完午飯?zhí)稍诖采纤⑴笥讶Γ⒌搅恕侗获R東轉(zhuǎn)發(fā)點贊,閱讀過百萬,這個92年妹子的筆記究竟有什么魔力》這篇文...
    蛋炒飯要加肉閱讀 384評論 6 1
  • 時間管理第13講,主要是對1-12講的一個總結(jié)和歸納。我們每天都會接收大量的資訊,但是太多的資訊猶如太少的...
    迭代鳳兒閱讀 444評論 0 0
  • 以前很喜歡看一些名人的演講稿,其實有些時候看的多了反而不記得都看了什么,權(quán)當(dāng)培養(yǎng)一些情操。但是我還是記住了...
    青草地與港灣閱讀 206評論 1 0