-
Notifications
You must be signed in to change notification settings - Fork 4
/
Cache.swift
173 lines (145 loc) · 5.54 KB
/
Cache.swift
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import Foundation
public enum CacheExpiry {
case Never
case Seconds(TimeInterval)
case Date(NSDate)
}
class Cache<T: NSCoding> {
let name: String
let cacheDirectory: String
private let cache = NSCache<AnyObject, AnyObject>()
private let fileManager = FileManager()
private let diskQueue: DispatchQueue = DispatchQueue(label: "com.katryo.cache.diskQueue")
init(name: String, directory: String?) {
self.name = name
cache.name = name
if let d = directory {
cacheDirectory = d
} else {
let dir = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first
cacheDirectory = dir!.appendingFormat("/com.katryo.cache/%@", name)
}
if !fileManager.fileExists(atPath: cacheDirectory) {
do {
try fileManager.createDirectory(atPath: cacheDirectory, withIntermediateDirectories: true, attributes: nil)
} catch _ {
}
}
}
convenience init(name: String) {
self.init(name: name, directory: nil)
}
func setObjectForKey(key: String, cacheBlock: ((T, CacheExpiry) -> (), (NSError?) -> ()) -> (), completion: @escaping (T?, Bool, NSError?) -> ()) {
if let object = objectForKey(key: key) {
completion(object, true,nil)
} else {
let successBlock: (T, CacheExpiry) -> () = { (obj, expires) in
self.setObject(object: obj, forKey: key, expires: expires)
completion(obj, false, nil)
}
let failureBlock: (NSError?) -> () = { (error) in
completion(nil, false, error)
}
cacheBlock(successBlock, failureBlock)
}
}
func objectForKey(key: String) -> T? {
var possibleObject = cache.object(forKey: key as AnyObject) as? CacheObject
if possibleObject == nil {
diskQueue.sync() {
let path = self.pathForKey(key: key)
if self.fileManager.fileExists(atPath: path) {
possibleObject = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? CacheObject
}
}
}
if let object = possibleObject {
if !object.isExpired() {
return object.value as? T
} else {
removeObjectForKey(key: key)
}
}
return nil
}
func setObject(object: T, forKey key: String) {
self.setObject(object: object, forKey: key, expires: .Never)
}
func setObject(object: T, forKey key: String, expires: CacheExpiry) {
let expiryDate = expiryDateForCacheExpiry(expiry: expires)
let cacheObject = CacheObject(value: object, expiryDate: expiryDate)
cache.setObject(cacheObject, forKey: key as AnyObject)
diskQueue.async() {
let path = self.pathForKey(key: key)
NSKeyedArchiver.archiveRootObject(cacheObject, toFile: path)
}
}
func removeObjectForKey(key: String) {
cache.removeObject(forKey: key as AnyObject)
diskQueue.async() {
let path = self.pathForKey(key: key)
do {
try self.fileManager.removeItem(atPath: path)
} catch _ {
}
}
}
func removeAllObjects() {
diskQueue.async() {
self.cache.removeAllObjects()
let paths = (try! self.fileManager.contentsOfDirectory(atPath: self.cacheDirectory))
for key in paths {
let path = self.pathForKey(key: key)
print("removing object in cache")
do {
try self.fileManager.removeItem(atPath: path)
} catch _ {
}
}
}
}
func removeExpiredObjects() {
diskQueue.async() {
let paths = (try! self.fileManager.contentsOfDirectory(atPath: self.cacheDirectory))
let keys = paths.map { NSURL(fileURLWithPath: $0).deletingPathExtension?.absoluteString }
for key in keys {
let object = self.cache.object(forKey: key as AnyObject) as? CacheObject
if object!.isExpired() {
self.removeObjectForKey(key: key!)
}
}
}
}
// MARK: Subscripting
subscript(key: String) -> T? {
get {
return objectForKey(key: escapeSlashes(key: key))
}
set(newValue) {
if let value = newValue {
setObject(object: value, forKey: escapeSlashes(key: key))
} else {
removeObjectForKey(key: escapeSlashes(key: key))
}
}
}
func pathForKey(key: String) -> String {
let directoryURL = NSURL(string: cacheDirectory)
let escapedKey = escapeSlashes(key: key)
let pathURL = directoryURL!.appendingPathComponent(escapedKey)
return pathURL!.absoluteString
}
private func expiryDateForCacheExpiry(expiry: CacheExpiry) -> NSDate {
switch expiry {
case .Never:
return NSDate.distantFuture as NSDate
case .Seconds(let seconds):
return NSDate().addingTimeInterval(seconds)
case .Date(let date):
return date
}
}
private func escapeSlashes(key: String) -> String {
return key.replacingOccurrences(of: "/", with: "slash", options: [], range: nil)
}
}