先講一下爲何要去封裝這個單例類.一開始我是怎麼進行數據的存儲的?寫一個單例而後添加屬性,修改屬性的Set方法,爲了防止手寫失誤,還要定義宏常量.在刪除的時候,不但要把屬性置爲nil,還要把NSUserDefaults的值置空,至關繁瑣複雜,每增長一個屬性就要增長最少十行代碼.很是不利於管理.git
在沒有封裝SDUserDefaults,多少在深夜中驚醒,生怕由於本身的疏忽,致使一些用戶數據存儲不上,可真是往事不堪回首吶,因此利用本身的一個空閒時間,封裝一個SDUserDefaults存儲單例,省去繁瑣的步驟.使用起來簡單粗暴.下面咱們就來看一下如何使用SDUserDefaults這個單例類進行用戶數據存儲.github
1.先去Github的SDUserDefaults下載演示Demo以及SDUserDefaults.bash
2.把SDUserDefaults文件夾導入你本身的項目合適位置,文件夾中主要包含SDUserDefaults和SDCodingObject兩個類.工具
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的呢?主要用到了如下三點,分別是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];
}
}
複製代碼
再說一下runtime和NSCoding協議的配合使用,這個主要是在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就更好了~~哈哈),若是有任何問題,歡迎在評論區或者簡信聯繫我,謝謝你們了.