说起用户信息保存问题,每个开发者应该都有很多不一样的见解。iOS提供了多种本地数据持久化方式,常用的有沙盒机制、本地数据库等。使用最多,也最简单方便的估计就是NSUserDefaults了。NSUserDefaults也是沙盒机制的一种,他的沙盒路径是Library->Preferences,因为大家用的都多,这里就不介绍了。处于安全性考虑,一般我们保存的用户信息并不会包括账号、密码这类敏感数据(这类信息可以保存到钥匙串,或者加密以后再保存),博主这里说的用户信息指的就是类似用户昵称、头像、性别等对安全性要求不高的数据。
一、创建用户信息数据模型
XPYUserModel.h
/// XPYBaseModel是博主之前创建的数据模型的基类,实现了YYModel协议(使用YYModel第三方库)
#import "XPYBaseModel.h"
NS_ASSUME_NONNULL_BEGIN
@interface XPYUserModel : XPYBaseModel // 归、解档操作必须实现NSCoding协议或者NSSecureCoding协议,我们当然使用保证数据安全性的NSSecureCoding
@property (nonatomic, copy) NSString *userId;
@property (nonatomic, copy) NSString *nickname;
@property (nonatomic, copy) NSString *age;
@end
NS_ASSUME_NONNULL_END
XPYUserModel.m
#import "XPYUserModel.h"
@implementation XPYUserModel
/// 必须实现的两个协议方法
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[self yy_modelEncodeWithCoder:coder];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
return [self yy_modelInitWithCoder:coder];
}
/// 必须实现supportsSecureCoding方法,返回YES
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
/// 因为博主使用了YYModel,所以两个协议方法中只是调用了YYModel的方法,若是自己写代码,如下
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.userId forKey:@"userId"];
[coder encodeObject:self.nickname forKey:@"nickname"];
[coder encodeInteger:self.age forKey:@"age"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
self = [super init];
if (self) {
self.userId = [coder decodeObjectForKey:@"userId"];
self.nickname = [coder decodeObjectForKey:@"nickname"];
self.age = [coder decodeIntegerForKey:@"age"];
}
return self;
}
二、创建归解档工具类
XPYArchiveManager.h
#import
NS_ASSUME_NONNULL_BEGIN
@interface XPYArchiveManager : NSObject
/// 归档对象
/// @param object 对象
/// @param fileName 文件名
+ (BOOL)archeveObject:(id)object fileName:(NSString *)fileName;
/// 归档对象
/// @param object 对象
/// @param directoryPath 文件夹路径,默认DocumentDirectory路径
/// @param directoryName 下一级文件夹名称
/// @param fileName 文件名
+ (BOOL)archeveObject:(id)object directoryPath:(NSString * _Nullable)directoryPath directoryName:(NSString *_Nullable)directoryName fileName:(NSString *)fileName;
/// 对象解档
/// @param objectClass 对象类
/// @param fileName 文件名
+ (id)unarcheveObjectWithClass:(id)objectClass fileName:(NSString *)fileName;
/// 对象解档
/// @param objectClass 对象类
/// @param directoryPath 文件夹路径,默认DocumentDirectory路径
/// @param directoryName 下一级文件夹名称
/// @param fileName 文件名
+ (id)unarcheveObjectWithClass:(id)objectClass directoryPath:(NSString * _Nullable)directoryPath directoryName:(NSString *_Nullable)directoryName fileName:(NSString *)fileName;
@end
NS_ASSUME_NONNULL_END
XPYArchiveManager.m
#import "XPYArchiveManager.h"
@implementation XPYArchiveManager
+ (BOOL)archeveObject:(id)object fileName:(NSString *)fileName {
return [self archeveObject:object directoryPath:nil directoryName:nil fileName:fileName];
}
+ (BOOL)archeveObject:(id)object directoryPath:(NSString *)directoryPath directoryName:(NSString *)directoryName fileName:(NSString *)fileName {
if (XPYIsEmptyObject(object)) { // XPYIsEmptyObject是简单判空宏,这里就不贴代码了
NSLog(@"对象为空");
return NO;
}
BOOL supportSecureCoding = NO;
if ([object conformsToProtocol:@protocol(NSSecureCoding)]) { // 安全编码
supportSecureCoding = YES;
} else if ([object conformsToProtocol:@protocol(NSCoding)]) { // 普通编码
supportSecureCoding = NO;
}
NSError *error = nil;
NSData *resultData = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:supportSecureCoding error:&error];
if (error || !resultData) {
NSLog(@"归档数据失败");
return NO;
}
NSString *filePath = [self filePathWithDirectoryPath:directoryPath directoryName:directoryName fileName:fileName];
if (!filePath) {
return NO;
}
return [resultData writeToFile:filePath atomically:YES];
}
+ (id)unarcheveObjectWithClass:(id)objectClass fileName:(NSString *)fileName {
return [self unarcheveObjectWithClass:objectClass directoryPath:nil directoryName:nil fileName:fileName];
}
+ (id)unarcheveObjectWithClass:(id)objectClass directoryPath:(NSString *)directoryPath directoryName:(NSString *)directoryName fileName:(NSString *)fileName {
NSString *filePath = [self filePathWithDirectoryPath:directoryPath directoryName:directoryName fileName:fileName];
if (!filePath) {
return nil;
}
NSData *resultData = [NSData dataWithContentsOfFile:filePath];
return [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass fromData:resultData error:nil];
}
/// 获取文件路径
/// @param directoryPath 文件夹路径
/// @param directoryName 下一级文件夹名称
/// @param fileName 文件名称
+ (NSString *)filePathWithDirectoryPath:(NSString * _Nullable)directoryPath directoryName:(NSString * _Nullable)directoryName fileName:(NSString *)fileName {
NSString *directory = directoryPath ? directoryPath : XPYDocumentDirectory;
if (directoryName) {
directory = [directory stringByAppendingPathComponent:directoryName];
}
if (!XPYCreateDirectoryAtPath(directory)) { // XPYCreateDirectoryAtPath是创建文件夹内联方法
NSLog(@"创建文件夹失败");
return nil;
}
return [directory stringByAppendingPathComponent:fileName];
}
@end
创建文件夹内联方法
/// 创建文件夹
/// @param path 文件夹路径
static inline BOOL XPYCreateDirectoryAtPath(NSString *path) {
BOOL isDirectory = NO;
// 路径下是否已存在文件或者文件夹
BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
if (isExist && isDirectory) {
// 已存在该文件夹
return YES;
}
if (isExist && !isDirectory) {
// 已存在文件,但不是文件夹,则删除文件
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
}
// 创建文件夹
return [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];;
}
三、测试
/// Document文件夹路径
#define XPYDocumentDirectory NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject
/// Document路径下文件夹名称
static NSString * const XPYMomentsDocumentDirectoryName = @"XPYMoments";
/// 保存用户信息文件名
static NSString * const XPYMomentsUserDataFileName = @"xpy_moments_user.data";
XPYUserModel *user = [[XPYUserModel alloc] init];
user.userId = @"123";
user.nickname = @"xxpy";
user.age = 28;
BOOL isArchived = [XPYArchiveManager archeveObject:user directoryPath:XPYDocumentDirectory directoryName:XPYMomentsDocumentDirectoryName fileName:XPYMomentsUserDataFileName];
if (isArchived) {
NSLog(@"归档成功");
XPYUserModel *temp = [XPYArchiveManager unarcheveObjectWithClass:[XPYUserModel class] directoryPath:XPYDocumentDirectory directoryName:XPYMomentsDocumentDirectoryName fileName:XPYMomentsUserDataFileName];
NSLog(@"解档结果:%@ %@ %@", temp.userId, temp.nickname, @(temp.age));
}
测试结果如下:
2020-07-24 11:11:30.507884+0800 XPYMoments[12718:542155] 归档成功
2020-07-24 11:11:30.508347+0800 XPYMoments[12718:542155] 解档结果:123 xxpy 28