沙盒和列表数据缓存

首页 / iOS / 正文

沙盒

沙盒是什么

沙盒:是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

以及沙盒中所包含的文件

sandbox.png

沙盒文件夹的作用

Documents

保存持久化的数据,例如用户的登录信息、搜索记录等一些关键数据。系统会为存储在这个文件夹下的数据进行备份

Library

Library中包含Caches和Preferences两个文件夹

Library.png

Caches:用于存储缓存数据,且不会被系统备份

Preferences:用于保存持久化数据,NSUserDefaults就存放在此文件夹中,会被系统备份

SystemData

新加入到沙盒中的文件,具体作用未知

tmp

用于存放临时数据,应用退出时会清除该目录下的数据,且不会被系统备份。

列表数据缓存

在理解了沙盒的机制之后,我们将通过沙盒来缓存我们加载到cell的列表数据,以便用户在第二次打开时优先展示本地数据而不是空cell。

数据归档

我们需要将网络请求的数据实时储存到沙盒中进行保存。在这里我们将数据写入Lilbrary/Cache/Data/list中

  1. 使用NSSearchPathForDirectoriesInDomains获取沙盒路径
  2. 在沙盒中创建文件夹路径
  3. 使用FileManager创建目录和文件
  4. 讲对象(这里是类型数组)序列化成Data
  5. 将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)
        }
        //网络请求
        ....
  }

最终效果

sand.gif

评论区
头像
文章目录