沙盒
沙盒是什么
沙盒:是iOS中每一个应用的独立储存空间。每个应用都只能访问自己沙盒中的文件,无法相互通信。当应用需要请求或接受沙盒外的数据时,都需要经过权限认证,否则无法获取到数据。
沙盒路径以及文件类型
在模拟器运行环境下得到的沙盒根路径(每次编译运行都会得到新的沙盒路径,当使用真机运行时则不会生成新的沙盒)
let homePath = NSHomeDirectory()
print(homePath)
///Users/wangtao/Library/Developer/CoreSimulator/Devices/FBE21BF5-1A14-4730-8BD1-2E62809CBAD2/data/Containers/Data/Application/007C7152-B54A-419C-8779-C9445B2C67EF
以及沙盒中所包含的文件
沙盒文件夹的作用
Documents
保存持久化的数据,例如用户的登录信息、搜索记录等一些关键数据。系统会为存储在这个文件夹下的数据进行备份
Library
Library中包含Caches和Preferences两个文件夹
Caches:用于存储缓存数据,且不会被系统备份
Preferences:用于保存持久化数据,NSUserDefaults就存放在此文件夹中,会被系统备份
SystemData
新加入到沙盒中的文件,具体作用未知
tmp
用于存放临时数据,应用退出时会清除该目录下的数据,且不会被系统备份。
列表数据缓存
在理解了沙盒的机制之后,我们将通过沙盒来缓存我们加载到cell的列表数据,以便用户在第二次打开时优先展示本地数据而不是空cell。
数据归档
我们需要将网络请求的数据实时储存到沙盒中进行保存。在这里我们将数据写入Lilbrary/Cache/Data/list中
- 使用
NSSearchPathForDirectoriesInDomains
获取沙盒路径 - 在沙盒中创建文件夹路径
- 使用
FileManager
创建目录和文件 - 讲对象(这里是类型数组)序列化成Data
- 将Data写入文件
在此我们引入一个概念序列化
自定义对象是无法写入到文件的,必须转换成二进制流的格式。从对象到二进制数据的过程我们称为序列化,将二进制数据还原成对象的过程,我们称为反序列化。iOS中对象序列化需要实现NSCoding类协议,实现序列化与反序列化方法
在此我们通过NSScureCoding
实现序列化以及反序列化。
public class NewsModel: NSObject, NSSecureCoding {
public static var supportsSecureCoding: Bool = true
// Coder 转 Obj
public required init?(coder: NSCoder) {
author_name = coder.decodeObject(forKey: "author_name") as! String
category = coder.decodeObject(forKey: "category") as! String
date = coder.decodeObject(forKey: "date") as! String
thumbnail_pic_s = coder.decodeObject(forKey: "thumbnail_pic_s") as! String
title = coder.decodeObject(forKey: "title") as! String
uniquekey = coder.decodeObject(forKey: "uniquekey") as! String
url = coder.decodeObject(forKey: "url") as! String
}
public var author_name: String
public var category: String
public var date: String
public var thumbnail_pic_s: String
public var title: String
public var uniquekey: String
public var url: String
init(author_name: String, category: String, date: String, thumbnail_pic_s: String, title: String, uniquekey: String, url: String) {
self.author_name = author_name
self.category = category
self.date = date
self.thumbnail_pic_s = thumbnail_pic_s
self.title = title
self.uniquekey = uniquekey
self.url = url
}
// Obj 转 Coder
public func encode(with coder: NSCoder) {
coder.encode(author_name, forKey: "author_name")
coder.encode(category, forKey: "category")
coder.encode(date, forKey: "date")
coder.encode(thumbnail_pic_s, forKey: "thumbnail_pic_s")
coder.encode(title, forKey: "title")
coder.encode(uniquekey, forKey: "uniquekey")
coder.encode(url, forKey: "url")
}
}
在实现完Model的协议方法之后,我们将对象序列化成二进制数据
private func archiveData(data: [NewsModel]) {
let cacheArray = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
if let cachePath = cacheArray.first {
let fileManager = FileManager.default
let dataPath = cachePath.appending("/Data/")
do {
try fileManager.createDirectory(atPath: dataPath, withIntermediateDirectories: true, attributes: nil)
let listDataPath = dataPath.appending("list")
do {
// Obj 转 Data
let listData = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
fileManager.createFile(atPath: listDataPath, contents: listData, attributes: nil)
}
} catch {
print("error")
}
}
}
当我们网络请求成功后,我们应该重写本地数据
public func loadNetWork(block: @escaping ((Bool, Array<NewsModel>) -> Void)) {
// 本地数据加载优先于网络加载
if let localData = loadDataFromLocal() {
block(true, localData)
}
// 网络加载
let urlString = "http://v.juhe.cn/toutiao/index?type=top&key=1113811726e766953e642681e1371677"
let url = URL(string: urlString)!
let session = URLSession.shared
let task = session.dataTask(with: url) { [self] data, _, error in
guard error == nil else {
fatalError("Task Error")
}
guard let jsonData = data else {
fatalError("Data Error")
}
do {
// Data 转 JSON
guard let dic = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [String: Any] else {
fatalError("JSONSerialization Error")
}
guard let resultDic = dic["result"] as? [String: Any] else {
fatalError("JSONSerialization Error")
}
guard let dataArray = resultDic["data"] as? [Any] else {
fatalError("JSONSerialization Error")
}
var modelArray = [NewsModel]()
for item in dataArray {
let dic = item as! [String: String]
let model = NewsModel(author_name: dic["author_name"]!, category: dic["category"]!, date: dic["date"]!, thumbnail_pic_s: dic["thumbnail_pic_s"]!, title: dic["title"]!, uniquekey: dic["uniquekey"]!, url: dic["url"]!)
modelArray.append(model)
}
archiveData(data: modelArray)
// 主线程刷新
DispatchQueue.main.async {
block(true, modelArray)
}
} catch {
// error
}
}
task.resume()
}
反归档
将本地文件反序列化为对象数组
private func loadDataFromLocal() -> [NewsModel]? {
let homePath = NSHomeDirectory()
print(homePath)
let cacheArray = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
if let cachePath = cacheArray.first {
// 创建文件管理器
let fileManager = FileManager.default
// 文件路径
let listDataPath = cachePath.appending("/Data/list")
// 文件数据加载为Data
if let readListData = fileManager.contents(atPath: listDataPath) {
// Data 转 Obj
if let listDataArray = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NewsModel.self, NSArray.self], from: readListData) as? [NewsModel] {
return listDataArray
}
}
}
return nil
}
当我们开始网络请求之前应该先从本地读取数据加载到cell,优化用户体验
public func loadNetWork(block: @escaping ((Bool, Array<NewsModel>) -> Void)) {
// 本地数据加载优先于网络加载
if let localData = loadDataFromLocal() {
block(true, localData)
}
//网络请求
....
}