SDUserDefaults:存儲用戶信息太痛苦?用這個就夠了~


前言


先講一下爲何要去封裝這個單例類.一開始我是怎麼進行數據的存儲的?寫一個單例而後添加屬性,修改屬性的Set方法,爲了防止手寫失誤,還要定義宏常量.在刪除的時候,不但要把屬性置爲nil,還要把NSUserDefaults的值置空,至關繁瑣複雜,每增長一個屬性就要增長最少十行代碼.很是不利於管理.git

在沒有封裝SDUserDefaults,多少在深夜中驚醒,生怕由於本身的疏忽,致使一些用戶數據存儲不上,可真是往事不堪回首吶,因此利用本身的一個空閒時間,封裝一個SDUserDefaults存儲單例,省去繁瑣的步驟.使用起來簡單粗暴.下面咱們就來看一下如何使用SDUserDefaults這個單例類進行用戶數據存儲.github


SDUserDefaults 使用


1.先去Github的SDUserDefaults下載演示Demo以及SDUserDefaults.bash

2.把SDUserDefaults文件夾導入你本身的項目合適位置,文件夾中主要包含SDUserDefaultsSDCodingObject兩個類.工具

3.在SDUserDefaults的.h文件中添加你想要存儲的屬性,這裏須要注意的是屬性必須是遵循NSCoding協議的類,Foundation中的類都已經遵循該協議.以下圖所示.ui

這時候有人會問,那我自定義的類須要怎麼辦?難道我須要本身實現NSCoding協議中的**- (void)encodeWithCoder- (instancetype)initWithCoder方法嗎?徹底不須要!你須要繼承於SDCodingObject**這個類便可,我在其中都作了NSCoding協議的實現,而且全部的屬性都會進行歸檔操做.例如上圖的TestModel類.代碼以下所示.this

4.存儲數據:只須要咱們把對應的屬性進行賦值,而後調用saveUserInfoAction方法便可.代碼以下所示.spa

[SDUserDefaults standardUserDefaults].name = @"用戶數據";
    TextModel *testModel = [[TextModel alloc] init];
    testModel.name = @"騷棟";
    testModel.age = @(15);
    testModel.location = @"北京";
    [SDUserDefaults standardUserDefaults].testModel = testModel;
    [[SDUserDefaults standardUserDefaults] saveUserInfoAction]; // 存儲數據
複製代碼

5.獲取數據:直接取值就好,簡單粗暴,沒有任何問題.代碼以下所示.code

/*****獲取數據*****/
    NSLog(@"%@",[SDUserDefaults standardUserDefaults].name);
    NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.name);
    NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.age);
    NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.location);
複製代碼

6.刪除數據:想要刪除數據直接調用deleteUserInfo便可.component

[[SDUserDefaults standardUserDefaults] deleteUserInfo];
複製代碼

7.更新數據:想要刪除的話,就把那個屬性置爲nil,想要修改某個屬性就把那個屬性修改,最後調用saveUserInfoAction方法保存便可便可.orm

[SDUserDefaults standardUserDefaults].name = @"新的用戶數據";
    [SDUserDefaults standardUserDefaults].testModel.location = nil;
    [[SDUserDefaults standardUserDefaults] saveUserInfoAction]; // 更新數據
複製代碼

通過上面的步驟,咱們就知道了如何使用SDUserDefaults,是否是很是的簡單,接下來,咱們看一下SDUserDefaults是如何實現的,只有瞭解原理,你才能在此基礎上更好的定製本身想要的效果.


SDUserDefaults 實現


如何實現 SDUserDefaults的呢?主要用到了如下三點,分別是runtime,NSCoding協議,NSUserDefaults.

先說一下NSUserDefaults在其中扮演的角色,雖然NSUserDefaults仍是做爲存儲空間使用,可是已經不僅僅是每個單例屬性都要進行一遍操做,由於SDUserDefaults已經遵循了NSCoding協議,因此咱們能夠直接進行歸檔存儲,代碼以下所示.

- (void)saveUserInfoAction {
    
    NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self];
    [[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:SD_USER_MANAGER];
}
複製代碼

那麼何時進行從NSUserDefaults中進行取值呢?那就在單例建立的時候,判斷一下NSUserDefaults是否含有歸檔數據,若是有,則進行歸檔,沒有則進行空白初始化,代碼以下所示.

static SDUserDefaults *userDefaults = nil;

+ (instancetype)standardUserDefaults {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (userDefaults == nil) {
            userDefaults = [SDUserDefaults initUserInfoAction];
        }
    });
    
    return userDefaults;
}

+ (instancetype)initUserInfoAction {
    
    NSData *userInfoData = [[NSUserDefaults standardUserDefaults] objectForKey:SD_USER_MANAGER];
    if (userInfoData == nil) {
        return [[SDUserDefaults alloc] init];
    } else {
        return [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
    }
}
複製代碼

再說一下runtimeNSCoding協議的配合使用,這個主要是在SDCodingObject中進行了實現,利用runtime的方法遍歷出全部的屬性以及屬性值,而後進行歸檔和解檔操做.代碼以下所示.

#pragma mark - 歸檔與解檔

- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
    
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t *thisProperty = &propertyList[i];
        const char *name = property_getName(*thisProperty);
        NSString *propertyName = [NSString stringWithFormat:@"%s",name];
        id propertyValue = [self valueForKey:propertyName];
        [aCoder encodeObject:propertyValue forKey:propertyName];
    }
    free(propertyList);
}

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
    
    if (self = [super init]) {
        
        unsigned int propertyCount = 0;
        objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
        for (int i = 0; i < propertyCount; i++) {
            objc_property_t *thisProperty = &propertyList[i];
            const char *name = property_getName(*thisProperty);
            NSString *propertyName = [NSString stringWithFormat:@"%s",name];
            [self setValue:[aDecoder decodeObjectForKey:propertyName] forKey:propertyName];
        }
        free(propertyList);
    }
    return self;
}
複製代碼

固然了,我怕有些人不知道什麼類沒有遵循NSCoding協議,因此我在SDCodingObject初始化的時候就會檢測全部的屬性是否遵循了NSCoding協議,若是沒有就會直接拋出異常,讓老鐵們能快速知道是什麼屬性沒有遵循協議,方便問題的查找,代碼以下所示.

// 檢測全部成員變量是否遵循NSCoding協議
- (void)testPropertyConformsToNSCodingProtocol {
    
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t thisProperty = propertyList[i];
        const char * type = property_getAttributes(thisProperty);
        NSString * typeString = [NSString stringWithUTF8String:type];
        NSArray * attributes = [typeString componentsSeparatedByString:@","];
        NSString * typeAttribute = [attributes objectAtIndex:0];
        if ([typeAttribute hasPrefix:@"T@"] && [typeAttribute length] > 1) {
            NSString * typeClassName = [typeAttribute substringWithRange:NSMakeRange(3, [typeAttribute length]-4)];  //turns @"NSDate" into NSDate
            Class typeClass = NSClassFromString(typeClassName);
            
            BOOL isConforms = [typeClass conformsToProtocol:@protocol(NSCoding)];
            if (!isConforms) {
                NSString *exceptionContent = [NSString stringWithFormat:@"%@ 類中的 %@屬性 未遵循NSCoding協議,請手動調整",NSStringFromClass([self class]),typeClassName];
                @throw [NSException exceptionWithName:@"property has not NSCoding Protocol" reason:exceptionContent userInfo:nil];
            }
        }
    }
    free(propertyList);
}
複製代碼

固然了,在刪除用戶數據的時候也是用到了runtime,這樣是很是方便的.代碼以下所示.

- (void)deleteUserInfo {
    
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
    for (int i = 0; i < propertyCount; i++) {
        objc_property_t *thisProperty = &propertyList[i];
        const char *name = property_getName(*thisProperty);
        NSString *propertyName = [NSString stringWithFormat:@"%s",name];
        [self setValue:nil forKey:propertyName];
    }
    free(propertyList);
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:SD_USER_MANAGER];
}
複製代碼

後語


SDUserDefaults算是一個小工具類吧,有須要的直接拿走不謝,最後再一次附上Github的傳送門(能點個star就更好了~~哈哈),若是有任何問題,歡迎在評論區或者簡信聯繫我,謝謝你們了.

####→ SDUserDefaults的傳送門


相關文章
相關標籤/搜索