归档
归档操作
我们需要将加载App网络请求的数据归档实时写入沙盒中保存。
iOS沙盒机制可以查看此文章
我们这里将网络数据写入到Cache目录下Data目录下的list文件。
- 使用
NSSearchPathForDirectoriesInDomains
取出沙盒路径。 - 根据沙盒路径创建需创建文件路径。
- 使用
FileManager
文件管理器创建相应的目录和文件。 - 将对象(这里是类型数组)序列化成
Data
- 将data归档写入文件。
这里有一个概念:序列化。
自定义对象是无法写入到文件的,必须转换成二进制流的格式。从对象到二进制数据的过程我们称为序列化,将二进制数据还原成对象的过程,我们称为反序列化。iOS中对象序列化需要实现NSCoding类协议,实现序列化与反序列化方法。
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)coder;
- (nullable instancetype)initWithCoder:(NSCoder *)coder; // NS_DESIGNATED_INITIALIZER
@end
我们这里实现NSScureCoding
,更加安全。
mport UIKit
// 新闻Model,实现NSSecureCoding协议(序列化与反序列化)
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的协议方法编写后,我们便能轻松将对象序列化成二进制数据。
/// 数据归档
/// - Parameter data: 网络数据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")
}
}
}
归档时机
我们应该是在获取新的数据后去重写本地文件数据,这里指的是网络请求成功后重写本地数据。
/// 网络加载获取数据
/// - Parameter block: 数据更新闭包
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=97ad001bfcc2082e2eeaf798bad3d54e"
let url = URL(string: urlString)!
let session = URLSession.shared
let task = session.dataTask(with: url) { [self] (data, response, 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()
}
在网络数据成功转为对象数组时,我们调用archiveData(data:)
方法,来达到效果。
反归档
反归档操作
将本地文件数据转成对象数组。也就是文件数据反序列化转成对象数组。
/// 本地数据加载
/// - Returns: 本地数据Model数组
private func loadDataFromLocal() -> [NewsModel]? {
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,优化用户体验。而不是在网络数据请求成功请一直以白色的Cell展示。
public func loadNetWork(block: @escaping ((Bool, Array<NewsModel>) -> Void)) {
// 本地数据加载优先于网络加载,优化用户体验
if let localData = loadDataFromLocal() {
block(true, localData)
}
// 网络加载
...
}
}
总结
通过这次案例,我们利用沙盒和序列化与反序列化的知识实现了基础的本地数据存储,这是一种简单基础的数据存储方案。