
Preface
Let's start by explaining why we need to encapsulate this singleton class. How do I store data in the first place? Write a singleton, then add attributes, modify the Set method of attributes, and define macro constants to prevent handwriting errors. When deleting, not only set the attribute to nil, but also leave the value of NSUserDefaults empty. It's cumbersome and complex, adding at least 10 lines for each attributeCode. Very unmanageable.
Without encapsulating SDUserDefaults, I woke up more or less in the middle of the night for fear that some user data could not be stored due to my negligence. It's hard to recall the past. So I used one of my free times to encapsulate an SDUserDefaults storage case without the tedious steps. It's easy and rough to use. Let's see how to use SDUserDefaults as a single case class.Row user data storage.
SDUserDefaults uses
1. Go first Github's SDU SerDefaults Download Demo and SDUserDefaults.
2. Import the SDUserDefaults folder into the appropriate location for your own project. The folder contains two main classes: SDUserDefaults and SDCodingObject.

3. Add the attributes you want to store in the.h file of SDU SerDefaults. Note here that the attributes must be classes that follow the NSCoding protocol. The classes in Foundation already follow that protocol. As shown in the following figure.

At this point, someone will ask, what do I need to do with my custom classes? Do I need to implement the -(void)encodeWithCoder and -(instancetype)initWithCoder methods in the NSCoding protocol myself? Not at all! You need to inherit from the class SDCodingObject, where I have implemented the NSCoding protocol and all the properties are archived. For example, the TestMode shown aboveClass L. The code is shown below.

4. Store data: Just assign the corresponding properties and call the saveUserInfoAction method. The code is shown below.
[SDUserDefaults standardUserDefaults].name = @"user data"; TextModel *testModel = [[TextModel alloc] init]; testModel.name = @"Sao Dong"; testModel.age = @(15); testModel.location = @"Beijing"; [SDUserDefaults standardUserDefaults].testModel = testModel; [[SDUserDefaults standardUserDefaults] saveUserInfoAction]; // Store data
5. Get data: Just value it directly, it's simple and rude, it's OK. The code is shown below.
/*****Getting Data*****/ NSLog(@"%@",[SDUserDefaults standardUserDefaults].name); NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.name); NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.age); NSLog(@"%@",[SDUserDefaults standardUserDefaults].testModel.location);
6. Delete data: Call deleteUserInfo directly if you want to delete data.
[[SDUserDefaults standardUserDefaults] deleteUserInfo];
7. Update the data: if you want to delete, set that property to nil, if you want to modify a property, modify that property, and finally call the saveUserInfoAction method to save.
[SDUserDefaults standardUserDefaults].name = @"New User Data"; [SDUserDefaults standardUserDefaults].testModel.location = nil; [[SDUserDefaults standardUserDefaults] saveUserInfoAction]; // Update Data
After the steps above, we know how to use SDUserDefaults. Is it very simple? Next, let's take a look at how SDUserDefaults are implemented. Only by understanding the principles, can you better customize the effect you want on this basis?
SDUserDefaults implementation
How to implement SDUserDefaults? There are three main points, runtime,NSCoding protocol and NSUserDefaults.
First, let's talk about the role NSUserDefaults plays. Although NSUserDefaults are still used as storage space, it's not just a single individual property that needs to be manipulated once. SDUserDefaults already follow the NSCoding protocol, so we can archive directly, as shown below.
- (void)saveUserInfoAction { NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self]; [[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:SD_USER_MANAGER]; }
So when does it take a value from NSUserDefaults? Then, when the singleton is created, determine if NSUserDefaults contains archived data, if so, archive it, and if not, initialize it blankly, as shown below.
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]; } }
Again, the use of runtime with the NSCoding protocol, which is mainly implemented in SDC odingObject, uses runtime to iterate through all the attributes and their values, then archives and archives them. The code is shown below.
#pragma mark - Archiving and Unfiling - (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; }
Of course, I'm afraid some people don't know what class doesn't follow the NSCoding protocol, so when I initialize the SDC odingObject, I will detect if all the attributes follow the NSCoding protocol, and if not, throw an exception directly, so that the old fellow can quickly know what attributes don't follow the protocol, so that they can easily find out the problem. The code is shown below.
// Detect whether all member variables follow the NSCoding protocol - (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:@"%@ In class %@Attribute not followed NSCoding Agreement,Please adjust manually",NSStringFromClass([self class]),typeClassName]; @throw [NSException exceptionWithName:@"property has not NSCoding Protocol" reason:exceptionContent userInfo:nil]; } } } free(propertyList); }
Of course, runtime is also used when deleting user data, which is very convenient. The code is shown below.
- (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]; }
Postscript
SDUserDefaults is a widget class, take it away when you need it, and finally attach Github's door once more (it would be better if you could order a star~~Ha-ha). If you have any questions, please contact me in the comments section or simply. Thank you.
_Transport door for SDUserDefaults
