数据 存储 本地持久化 路径 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();//主线程回调 }); }