数据
存储
本地持久化
路径
Documents
常用的目录,存放重要的数据,iTunes同步时会备份该目录
Library/Caches
一般存放体积大,不重要的数据,iTunes同步时不会备份该目录
像sd,AF,YY等都是存储在此目录下
Library/Preferences
存放用户的偏好设置,iTunes同步时会备份该目录
Library/Cookies
请求缓存
tmp
存放临时文件,非重要,随时可能会被系统删除该文件夹中的数据,iTunes同步时
不会备份该目录
SystemData
系统存储的数据
本地存储方式
NSUserDefaults、Plist、NSKeyedArchiver、SQLite3、Core Data、
Keychain、FMDB , realm等等
注意 : 数据存储要注意资源竞争问题, 了解加锁(同步/异步)和多线程相关知识
NSUserDefaults
[NSUserDefaults standardUserDefaults]
存 setObject:forKey
取 objectForKey:
只能存OC原生数据类型,且不可变对象, 非自定义对象
NSString、NSDictionary、NSArray、NSData、NSNumber等类型
字典存储不能含值null
数据类型要求较高,不建议直接存储后台返回的对象.不可控
建议先转utf8 json string 存储
plist
数据类型限制同NSUserDefaults
NSKeyedArchiver归档(NSCoding)
必须遵循NSCoding协议
@interface Person : NSObject<NSCoding>
归档
[aCoder encodeObject:self.name forKey:@"name"];
解档
self.name = [aDecoder decodeObjectForKey:@"name"];
可存自定义对象
SQLite3
导入libsqlite3.0.tbd系统框架
#import <sqlite3.h>
基于sql语法操作
创建数据库UserDB
// 创建数据库路径
NSString *dbPath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES)objectAtIndex:
0]stringByAppendingPathComponent:@“UserDB.sqlite"];
// 创建或者打开数据库
const char *p = [dbPath UTF8String];
int res = sqlite3_open(p, &db);
self.res = res;
打开数据库
if(self.res == SQLITE_OK) { //数据库成功打开 }
下面都是在打开成功下执行
创建表UserTable
NSString *sql = @"create table if not exists UserTable (t_id integer
primary key autoincrement, t_name varchar(20))";
if ([self execNoQueryWithSQL:sql]) {
//创建表格成功
} else {
//创建表格失败
}
增
NSString *insert_sql = @"insert into UserTable (t_name) values('小明')";
if ([self execNoQueryWithSQL:insert_sql]) {
//成功插入数据
}
删
NSString *delete_sql = @"delete from UserTable where t_id=1";
if ([self execNoQueryWithSQL:delete_sql]) {
//成功删除数据
}
改
NSString *update_sql = @"update UserTable set t_name='小明' where
t_id=1";
if ([self execNoQueryWithSQL:update_sql]) {
//成功更新数据
}
查
int seachId = 1;//查询的id
NSString *seach_name = @"小%";//模糊查询,小开头
NSString *seach_sql = @"select * from UserTable where t_id>? and
t_name like ?";
sqlite3_stmt *stmt = [self execQueryWithSQL:seach_sql
andWithParams:@[[NSNumber numberWithInt:seachId],seach_name]];
//准备执行(相当于点击run query),执行的时候是一行一行的执行
while (sqlite3_step(stmt) == SQLITE_ROW) {
//按照当前列的类型选数据,列数从0开始
int t_id = sqlite3_column_int(stmt, 0);
const unsigned char *t_name = sqlite3_column_text(stmt, 1);
NSString *name = [NSString stringWithUTF8String:(char*)t_name];
}
sqlite3_finalize(stmt);
Core Data
对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据
库文件中,也能够将保存在数据库中的数据还原成OC对象
延迟加载
使用时获取相应的关联对象
默认情况下,利用Core Data取出的实体都是NSManagedObject类型的,能够
利用键-值对来存取数据
事先创建对应文件,设置关系表(可视化)
模型路径
//获取模型路径
NSURL *modelURL = [[NSBundle mainBundle]
URLForResource:@"CoreData__" withExtension:@"momd"];
//根据模型文件创建模型对象
NSManagedObjectModel *model = [[NSManagedObjectModel alloc]
initWithContentsOfURL:modelURL];
利用模型对象创建助理对象
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator
alloc] initWithManagedObjectModel:model];
数据库路径
NSURL *sqlUrl = [NSURL fileURLWithPath:[docStr
stringByAppendingPathComponent:@"coreData.sqlite"]];
添加数据库
NSError *error = nil;
NSPersistentStore *store = [psc
addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url
options:nil error:&error];
if (store == nil) { // 直接抛异常
[NSException raise:@"添加数据库错误" format:@"%@", [error
localizedDescription]];
}
添加数据
//传入上下文,创建一个Person实体对象
NSManagedObject *person = [NSEntityDescription
insertNewObjectForEntityForName:@"Person"
inManagedObjectContext:context];
//设置简单属性
[person setValue:@"MJ" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:27] forKey:@"age"];
//传入上下文,创建一个Card实体对象
NSManagedObject *card = [NSEntityDescription
insertNewObjectForEntityForName:@"Card"
inManagedObjectContext:context];
[card setValue:@"4414241933432" forKey:@"no"];
//设置Person和Card之间的关联关系
[person setValue:card forKey:@"card"];
//利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [context save:&error];
if (!success) {
[NSException raise:@"访问数据库错误" format:@"%@", [error
localizedDescription]];
}
查询数据
//初始化一个查询请求
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
//设置要查询的实体
NSEntityDescription *desc = [NSEntityDescription
entityForName:@"Person" inManagedObjectContext:context];
//设置排序(按照age降序)
NSSortDescriptor *sort = [NSSortDescriptor
sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
//设置条件过滤(name like '%Itcast-1%')
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like
%@", @"*Itcast-1*"];
request.predicate = predicate;
删除数据
//传入需要删除的实体对象
[context deleteObject:managedObject];
//将结果同步到数据库
NSError *error = nil;
[context save:&error];
if (error) {
[NSException raise:@"删除错误" format:@"%@", [error
localizedDescription]];
}
Keychain
SQLite API进行封装的库
可存储重要数据, 且卸载后数据不会被删除, 可用来记录用户安装登录记录
越狱下仍然不是安全的,重要信息依然可能被破解,需要加密存储,不要存储用户
账户密码明文
存储区
私有
该APP单独访问
共享
相同开发者APP可共享的区域
其他数据库
服务端的缓存
缓存
缓存的本质是用空间换取时间
重复相同的请求对服务器会造成压力,浪费资源, 所以缓存能更好的过滤掉不必要
的请求
首次请求
客户端: 会缓存数据
标识 Cache-Control、Expires和Etag
在请求头加上If-Modified-Since或者If-None-Match标识
再次请求
服务器: 对比客户端标识
相同的请求
状态码304:不返回数据
不同请求
状态码200:返回数据
高频请求缓存
服务器对高频请求结果进行缓存, 其他用户请求会立马返回结果
缓存有很短的有效期,且要定时更新,请求用户越多, 数据越新
NSURLCache
iOS系统提供的内存以及磁盘的综合缓存机制
NSURLCache对象被存储沙盒中Library/cache目录下
代码
在didFinishLaunchingWithOptions函数添加代码, 设置缓存和磁盘最大值
NSURLCache * sharedCache = [[NSURLCache alloc]
initWithMemoryCapacity:20 * 1024 * 1024 diskCapacity:100 * 1024 *
1024 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
缓存策略
NSURLRequestUseProtocolCachePolicy = 0 默认
NSURLRequestReloadIgnoringLocalCacheData = 1
不使用任何缓存
NSURLRequestReturnCacheDataElseLoad = 2
有缓存取,无缓存加载
NSURLRequestReturnCacheDataDontLoad = 3
有缓存取,无缓存失败
SDWebImage
由类SDImageCache实现
下面主要介绍相关具体源码实现代码
存储路径
存储路径 = 存储的文件路径 + 文件名
key =
图片的url
文件名 = MD5(key.UTF8String) + 图片格式后缀(jpg/png/...)
后缀获取不到的就不添加
模拟器某图片路径:
/Users/niexiaobo/Library/Developer/CoreSimulator/Devices/E995BE0D-
A86E-4750-8BD6-8ADC834DFF09/data/Containers/Data/Application/
AAD2B5AD-6571-4CD8-88D0-A2D8C5FA6E96/Library/Caches/default/
com.hackemist.SDWebImageCache.default/
00a9d7b5880bde768175cd32166cb469.jpg
sd默认在/Library/Caches/default/下创建
com.hackemist.SDWebImageCache.default文件夹用来存储图片数据
存储图片
1 方法
- (void)storeImage:(nullable UIImage *)image imageData:(nullable
NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
首先是存储方法名: 需要图片data, 存储对应的key, 是否存磁盘, 以及回调
2 非空判断
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
不符合条件的判断时,直接block回调空
3 保存到内存缓存中
// 如果需要保存到内存缓存中
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = image.sd_memoryCost;
[self.memCache setObject:image forKey:key cost:cost];
}
4 保存到磁盘中-
数据准备
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
在操作队列self.ioQueue中异步执行
@autoreleasepool : 操作过程对象在自动释放池里,及时释放
if (!data && image) {
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDWebImageCodersManager sharedInstance]
encodedDataWithImage:image format:format];
}
存储需要转换为NSData, 如果data为空,但是有image时, 需要再次转换data
如果没有任何数据可以检测图像格式,请检查其是否包含使用PNG或JPEG格式的
Alpha通道
encodedDataWithImage 开始转数据data
[self _storeImageDataToDisk:data forKey:key];
开始存储到磁盘中
非空判断在[self _storeImageDataToDisk:data forKey:key];中判断
5 保存到磁盘中-
开始存储
方法名
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:
(nullable NSString *)key
1 非空判断
if (!imageData || !key) {
return;
}
2 cache路径是否存在
if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath
withIntermediateDirectories:YES attributes:nil error:NULL];
}
1 首先判断disk cache的文件路径是否存在,不存在的话就创建一个
2 disk cache的文件路径是存储在_diskCachePath中的
3 image的key
// 根据image的key(一般情况下理解为image的url)组合成最终的文件路径
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
4 写入数据
// 根据存储的路径(cachePathForKey)和存储的数据(data)将其存放到iOS的文件
系统
[imageData writeToURL:fileURL
options:self.config.diskCacheWritingOptions error:nil];
5 是否需要iCloud云备份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES
forKey:NSURLIsExcludedFromBackupKey error:nil];
}
读取图片
1 内存缓存读取
//第一步先检查内存缓存中是否已存储:
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString
*)key {
return [self.memCache objectForKey:key];
}
使用SDImageCache的imageFromMemoryCacheForKey:取数据
2 磁盘中读取
//当缓存中没有靶中数据,接着从磁盘中读取
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString
*)key {
UIImage *diskImage = [self diskImageForKey:key];
//读取到数据,判断是否需要存储到缓存中
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
//磁盘读取具体实现方法:
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
NSData *data = [self diskImageDataForKey:key];
return [self diskImageForKey:key data:data];
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:
(nullable NSData *)data {
return [self diskImageForKey:key data:data options:0];
}
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:
(nullable NSData *)data options:(SDImageCacheOptions)options {
if (data) {
UIImage *image = [[SDWebImageCodersManager sharedInstance]
decodedImageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
BOOL shouldScaleDown = options &
SDImageCacheScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance]
decompressedImageWithImage:image data:&data
options:@{SDWebImageCoderScaleDownLargeImagesKey:
@(shouldScaleDown)}];
}
return image;
} else {
return nil;
}
}
3 网络请求
当磁盘也么有的时候,就需要去服务器下载图片了
删除图片
SDImageCache主要方法
removeImageForKeyfromDisk:withCompletion: 异步地将image从缓存(内存
缓存以及可选的磁盘缓存)中移除
clearMemory 清除内存缓存上的所有image
clearDisk 清除磁盘缓存上的所有image
cleanDisk 删除磁盘缓存上过期的image
方法名
- (void)deleteOldFilesWithCompletionBlock:(nullable
SDWebImageNoParamsBlock)completionBlock {
(1) 遍历的文件目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath
isDirectory:YES];
(2) 内容日期密钥
NSURLResourceKey cacheContentDateKey =
NSURLContentModificationDateKey;
NSURLResourceKey cacheContentDateKey =
NSURLContentAccessDateKey;
(3) 遍历diskCachePath这个文件夹中的所有目录url
// NSDirectoryEnumerationSkipsHiddenFiles表示不遍历隐藏文件
NSDirectoryEnumerator *fileEnumerator = [self.fileManager
enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL];
(4) 计算过期时间
maxCacheAge = 7 天 ;
默认一个星期过期 7 天 (可手动设置)
过期时间expirationDate = 当前时间 - maxCacheAge
如果最后修改时间小于过期时间, 则说明文件已过期,需要删除
(5) 创建对象, 记录当前已经使用的磁盘缓存大小
NSUInteger currentCacheSize = 0;
循环遍历所有文件
for (NSURL *fileURL in fileEnumerator) {
(6) 跳过错误和文件夹
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey]
boolValue]) {
continue;
}
(7) 筛选过期文件,存放到数组中
NSDate *modifiedDate = resourceValues[cacheContentDateKey];
if ([[modifiedDate laterDate:expirationDate]
isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}
(8) 累计已使用缓存大小
NSNumber *totalAllocatedSize =
resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
循环结束
}
(9)删除所有过期文件
for (NSURL *fileURL in urlsToDelete) {
[self.fileManager removeItemAtURL:fileURL error:nil];
}
(10)缓存超过最大限定值,
需要再删除一部分(默认一半)
需求 : 如果我们当前cache的大小已经超过了允许配置的缓存大小,那就删除已
经缓存的文件
思路 : 1) 把缓存按时间先后排序 , 2) 从老数据开始删除, 循环判断是否删到一定
比例
按时间排序
NSArray<NSURL *> *sortedFiles = [cacheFiles
keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2)
{
return [obj1[cacheContentDateKey]
compare:obj2[cacheContentDateKey]]; }];
删除
// 每次删除file后,就计算此时的cache的大小
for (NSURL *fileURL in sortedFiles) {
if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues =
cacheFiles[fileURL];
NSNumber *totalAllocatedSize =
resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
// 如果此时的cache大小已经降到期望的大小了,就停止删除文件了
if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
结束
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();//主线程回调
});
}