基于ObjectMapper的本地缓存

公司项目在之前是没有将首页数据做缓存的,用户体验不太好,所以现在需要将首页数据存到本地。

实现

首先考虑用NSKeyedArchiver,但是用这货需要遵守NSCoding协议,将模型里的每个属性确定类型。但我们项目用ObjectMapper解析json,就已经把模型中的每个属性都设置过映射关系了,他喵的难道还要我要再写一遍?
后来仔细想想,json文件无非就是array和dictionary,只不过其本身的类型由根节点来确定,而且NSArrayNSDictionary是有writeToFile的方法的,那么我把模型转为对应的NSArrayNSDictionary类型就好了啊,bingo~~

撸了半天,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import Foundation
import ObjectMapper
class CacheManager {
//路径
    static func filePath(_ fileName:String) -> URL?{
        let manager = FileManager.default
//往哪存,自己改
        guard let cachePath = manager.urls(for: .cachesDirectory, in: .userDomainMask).first else { return nil }
        let directory = cachePath.appendingPathComponent("buybuyARTCache")
        
        if !manager.fileExists(atPath: directory.path){
            do{
                try manager.createDirectory(atPath: directory.path, withIntermediateDirectories: true, attributes: nil)
            }catch{
                return nil
            }
        }
        let path = directory.appendingPathComponent(fileName)
        return path
    }
    
    @discardableResult
    static func setCache(object:BaseMappable, for fileName: String) -> Bool{
        return setCache(json: object.toJSON(), for: fileName)
    }
    
    /*使用时需显示声明类型,let a:Art = CacheManager.cache(for: "cacheName")
     参照http://stackoverflow.com/questions/27965439/cannot-explicitly-specialize-a-generic-function */
    static func cache<T: BaseMappable>(for fileName: String) -> T?{
        guard let jsonDict = cacheJson(for: fileName) as? [String: Any] else {return nil}
        return Mapper<T>().map(JSON: jsonDict)
    }
    
    //存数组
    @discardableResult
    static func setCache(array: [BaseMappable], for fileName: String) -> Bool{
        return self.setCache(json: array.map({$0.toJSON()}), for: fileName)
    }
    
    //取数组
    static func cacheArray<T: BaseMappable>(for fileName: String) -> Array<T>?{
        guard let jsonArray = cacheJson(for: fileName) as? [[String: Any]] else {return nil}
        return Mapper<T>().mapArray(JSONArray: jsonArray)
    }
    
    //存json
    @discardableResult
    static func setCache(json: Any, for fileName: String) -> Bool{
        guard let filePath = filePath(fileName) else {return false}
        if let array = json as? [Any]{
            let jsonArray = NSArray(array: array)
            return jsonArray.write(to: filePath, atomically: true)
        }else if let dict = json as? [String: Any]{
            let jsonDict = NSDictionary(dictionary: dict)
            return jsonDict.write(to: filePath, atomically: true)
        }else {
            return false
        }
    }
    
    //取json
    static func cacheJson(for fileName: String) -> Any?{
        guard let filePath = filePath(fileName) else {return nil}
        if let jsonArray = NSArray(contentsOf: filePath){
            return jsonArray
        }else if let jsonDict = NSDictionary(contentsOf: filePath){
            return jsonDict
        }else{
            return nil
        }
    }
}

原则上来说,CacheManager只支持遵循了BaseMappable协议的对象,因为模型都是用ObjectMapper解析出来的。但是后来考虑到这样似乎限制太大,所以又加了对json数据的缓存。

问题

其中碰到一个问题,就是这个函数是用来从沙盒里取数据的,使用的时候需要确定数据的类型

1
2
3
4
static func cache<T: BaseMappable>(for fileName: String) -> T?{
guard let jsonDict = cacheJson(for: fileName) as? [String: Any] else {return nil}
return Mapper<T>().map(JSON: jsonDict)
}

但是当调用的时候,编译器老是编译不过,提示什么Cannot explicitly specialize a generic function。后来上so查了以后才知道,当使用泛型函数时,如果返回值是关联类型的话,需要显示声明

1
let a: Art = CacheManager.cache(for: "cacheName")

学艺不精~~(⊙﹏⊙)b,惭愧惭愧