Ver código fonte

Refactor AvatarCache to ImageCache

Use Cache library (https://github.com/hyperoslo/Cache) for caching
swiftui-preferences
Shadowfacts 1 ano atrás
pai
commit
982e42ca2f
Acessado por: Shadowfacts <me@shadowfacts.net> ID da chave GPG: 94A5AB95422746E5

+ 3
- 0
.gitmodules Ver arquivo

@@ -1,3 +1,6 @@
1 1
 [submodule "SwiftSoup"]
2 2
 	path = SwiftSoup
3 3
 	url = git://github.com/scinfu/SwiftSoup.git
4
+[submodule "Cache"]
5
+	path = Cache
6
+	url = git@github.com:hyperoslo/Cache.git

+ 1
- 0
Cache

@@ -0,0 +1 @@
1
+Subproject commit 8c42c575cf28b2ff0e780c9728721e9a8891c92e

+ 10
- 4
Tusker.xcodeproj/project.pbxproj Ver arquivo

@@ -12,8 +12,10 @@
12 12
 		04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04496BC8216252E5001F1B23 /* TTTAttributedLabel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
13 13
 		04496BD52162530A001F1B23 /* TTTAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD42162530A001F1B23 /* TTTAttributedLabel.m */; };
14 14
 		04496BD721625361001F1B23 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD621625361001F1B23 /* ContentLabel.swift */; };
15
+		0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; };
16
+		0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 17
 		04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
16
-		04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */; };
18
+		04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* ImageCache.swift */; };
17 19
 		04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ED00B021481ED800567C53 /* SteppedProgressView.swift */; };
18 20
 		D6028B9B2150811100F223B9 /* MastodonCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6028B9A2150811100F223B9 /* MastodonCache.swift */; };
19 21
 		D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
@@ -178,6 +180,7 @@
178 180
 				04496BD0216252E5001F1B23 /* TTTAttributedLabel.framework in Embed Frameworks */,
179 181
 				D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
180 182
 				D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
183
+				0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */,
181 184
 			);
182 185
 			name = "Embed Frameworks";
183 186
 			runOnlyForDeploymentPostprocessing = 0;
@@ -190,8 +193,9 @@
190 193
 		04496BCB216252E5001F1B23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
191 194
 		04496BD42162530A001F1B23 /* TTTAttributedLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TTTAttributedLabel.m; sourceTree = "<group>"; };
192 195
 		04496BD621625361001F1B23 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = "<group>"; };
196
+		0461A38F2163CBAE00C0A807 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
193 197
 		04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
194
-		04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.swift; sourceTree = "<group>"; };
198
+		04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
195 199
 		04ED00B021481ED800567C53 /* SteppedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SteppedProgressView.swift; sourceTree = "<group>"; };
196 200
 		D6028B9A2150811100F223B9 /* MastodonCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCache.swift; sourceTree = "<group>"; };
197 201
 		D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -337,6 +341,7 @@
337 341
 				04496BCF216252E5001F1B23 /* TTTAttributedLabel.framework in Frameworks */,
338 342
 				D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
339 343
 				D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
344
+				0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */,
340 345
 			);
341 346
 			runOnlyForDeploymentPostprocessing = 0;
342 347
 		};
@@ -667,6 +672,7 @@
667 672
 		D6D4DDC3212518A000E1C4BB = {
668 673
 			isa = PBXGroup;
669 674
 			children = (
675
+				0461A38F2163CBAE00C0A807 /* Cache.framework */,
670 676
 				D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
671 677
 				D61099AC2144B0CC00432DC2 /* Pachyderm */,
672 678
 				D61099B92144B0CC00432DC2 /* PachydermTests */,
@@ -697,7 +703,7 @@
697 703
 			children = (
698 704
 				D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
699 705
 				D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
700
-				04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
706
+				04DACE8D212CC7CC009840C4 /* ImageCache.swift */,
701 707
 				D6028B9A2150811100F223B9 /* MastodonCache.swift */,
702 708
 				D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
703 709
 				D6757A7A2157E00100721E32 /* XCallbackURL */,
@@ -1065,7 +1071,7 @@
1065 1071
 				D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
1066 1072
 				D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */,
1067 1073
 				D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */,
1068
-				04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */,
1074
+				04DACE8E212CC7CC009840C4 /* ImageCache.swift in Sources */,
1069 1075
 				D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */,
1070 1076
 				04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
1071 1077
 				D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */,

+ 3
- 0
Tusker.xcworkspace/contents.xcworkspacedata Ver arquivo

@@ -5,6 +5,9 @@
5 5
       location = "container:Tusker.xcodeproj">
6 6
    </FileRef>
7 7
    <FileRef
8
+      location = "group:Cache/Cache.xcodeproj">
9
+   </FileRef>
10
+   <FileRef
8 11
       location = "group:SwiftSoup/SwiftSoup.xcodeproj">
9 12
    </FileRef>
10 13
 </Workspace>

+ 0
- 61
Tusker/AvatarCache.swift Ver arquivo

@@ -1,61 +0,0 @@
1
-//
2
-//  ImageCache.swift
3
-//  Tusker
4
-//
5
-//  Created by  Shadowfactson 8/21/18.
6
-//  Copyright © 2018 Shadowfacts. All rights reserved.
7
-//
8
-
9
-import UIKit
10
-
11
-class AvatarCache {
12
-    
13
-    static let shared = AvatarCache()
14
-    
15
-    let cache = NSCache<NSString, UIImage>()
16
-    
17
-    var requests = [URL: URLSessionDataTask]()
18
-    var requestCallbacks = [URL: [(UIImage?) -> Void]]()
19
-    
20
-    
21
-    
22
-    private init() {
23
-    }
24
-    
25
-    func get(_ url: URL, completion: @escaping (UIImage?) -> Void) {
26
-        let key = url.absoluteString as NSString
27
-        if let image = cache.object(forKey: key) {
28
-            completion(image)
29
-        } else if var callbacks = requestCallbacks[url] {
30
-            callbacks.append(completion)
31
-            requestCallbacks[url] = callbacks
32
-        } else {
33
-            requestCallbacks[url] = [completion]
34
-            let task = URLSession.shared.dataTask(with: url) { data, response, error in
35
-                guard error == nil, let data = data, let image = UIImage(data: data) else {
36
-                        let callbacks = self.requestCallbacks.removeValue(forKey: url)
37
-                        callbacks?.forEach({ callback in
38
-                            // todo: default avatar for failed requests
39
-                            callback(nil)
40
-                        })
41
-                        return
42
-                }
43
-                
44
-                let callbacks = self.requestCallbacks.removeValue(forKey: url)
45
-                callbacks?.forEach({ callback in
46
-                    callback(image)
47
-                })
48
-                self.cache.setObject(image, forKey: key)
49
-            }
50
-            task.resume()
51
-            requests[url] = task
52
-        }
53
-    }
54
-    
55
-    func cancel(_ url: URL) {
56
-        requests[url]?.cancel()
57
-        requests.removeValue(forKey: url)
58
-        requestCallbacks.removeValue(forKey: url)
59
-    }
60
-    
61
-}

+ 85
- 0
Tusker/ImageCache.swift Ver arquivo

@@ -0,0 +1,85 @@
1
+//
2
+//  ImageCache.swift
3
+//  Tusker
4
+//
5
+//  Created by Shadowfacts on 8/21/18.
6
+//  Copyright © 2018 Shadowfacts. All rights reserved.
7
+//
8
+
9
+import UIKit
10
+import Cache
11
+
12
+class ImageCache {
13
+    
14
+    static let avatars = ImageCache(name: "Avatars")
15
+
16
+    let storage: Storage<UIImage>
17
+    
18
+    var requests = [URL: Request]()
19
+    
20
+    init(name: String, diskExpiry: Expiry = .seconds(60 * 60 * 24), memoryExpiry: Expiry = .seconds(60 * 60)) {
21
+        self.storage = try! Storage(
22
+            diskConfig: DiskConfig(name: name, expiry: diskExpiry),
23
+            memoryConfig: MemoryConfig(expiry: memoryExpiry),
24
+            transformer: TransformerFactory.forImage())
25
+    }
26
+    
27
+    func get(_ url: URL, completion: ((UIImage?) -> Void)?) {
28
+        let key = url.absoluteString
29
+        if (try? storage.existsObject(forKey: key)) ?? false,
30
+            let image = try? storage.object(forKey: key) {
31
+            completion?(image)
32
+        } else {
33
+            if let completion = completion, let request = requests[url] {
34
+                request.callbacks.append(completion)
35
+            } else {
36
+                let request = Request(url: url, completion: completion)
37
+                requests[url] = request
38
+                request.run { (image) in
39
+                    try? self.storage.setObject(image, forKey: key)
40
+                }
41
+            }
42
+        }
43
+    }
44
+    
45
+    func cancel(_ url: URL) {
46
+        requests[url]?.cancel()
47
+    }
48
+    
49
+    class Request {
50
+        let url: URL
51
+        var task: URLSessionDataTask?
52
+        var callbacks: [(UIImage?) -> Void]
53
+        
54
+        init(url: URL, completion: ((UIImage?) -> Void)?) {
55
+            if let completion = completion {
56
+                self.callbacks = [completion]
57
+            } else {
58
+                self.callbacks = []
59
+            }
60
+            self.url = url
61
+        }
62
+        
63
+        func run(cache: @escaping (UIImage) -> Void) {
64
+            task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
65
+                guard error == nil, let data = data, let image = UIImage(data: data) else {
66
+                    self.complete(with: nil)
67
+                    return
68
+                }
69
+                cache(image)
70
+                self.complete(with: image)
71
+            })
72
+            task!.resume()
73
+        }
74
+        
75
+        func cancel() {
76
+            task?.cancel()
77
+            complete(with: nil)
78
+        }
79
+        
80
+        func complete(with image: UIImage?) {
81
+            callbacks.forEach { $0(image) }
82
+        }
83
+    }
84
+    
85
+}

+ 1
- 1
Tusker/Screens/Compose/ComposeViewController.swift Ver arquivo

@@ -92,7 +92,7 @@ class ComposeViewController: UIViewController {
92 92
             inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
93 93
             inReplyToAvatarImageView.layer.masksToBounds = true
94 94
             inReplyToAvatarImageView.image = nil
95
-            AvatarCache.shared.get(inReplyTo.account.avatar) { image in
95
+            ImageCache.avatars.get(inReplyTo.account.avatar) { (image) in
96 96
                 DispatchQueue.main.async {
97 97
                     self.inReplyToAvatarImageView.image = image
98 98
                 }

+ 4
- 4
Tusker/Views/Notifications/ActionNotificationTableViewCell.swift Ver arquivo

@@ -75,7 +75,7 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
75 75
         usernameLabel.text = "@\(status.account.acct)"
76 76
         opAvatarImageView.image = nil
77 77
         opAvatarURL = status.account.avatar
78
-        AvatarCache.shared.get(status.account.avatar) { image in
78
+        ImageCache.avatars.get(status.account.avatar) { (image) in
79 79
             DispatchQueue.main.async {
80 80
                 self.opAvatarImageView.image = image
81 81
                 self.opAvatarURL = nil
@@ -83,7 +83,7 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
83 83
         }
84 84
         actionAvatarImageView.image = nil
85 85
         actionAvatarURL = notification.account.avatar
86
-        AvatarCache.shared.get(notification.account.avatar) { image in
86
+        ImageCache.avatars.get(notification.account.avatar) { (image) in
87 87
             DispatchQueue.main.async {
88 88
                 self.actionAvatarImageView.image = image
89 89
                 self.actionAvatarURL = nil
@@ -158,10 +158,10 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
158 158
     
159 159
     override func prepareForReuse() {
160 160
         if let url = opAvatarURL {
161
-            AvatarCache.shared.cancel(url)
161
+            ImageCache.avatars.cancel(url)
162 162
         }
163 163
         if let url = actionAvatarURL {
164
-            AvatarCache.shared.cancel(url)
164
+            ImageCache.avatars.cancel(url)
165 165
         }
166 166
         updateTimestampWorkItem?.cancel()
167 167
         updateTimestampWorkItem = nil

+ 2
- 2
Tusker/Views/Notifications/FollowNotificationTableViewCell.swift Ver arquivo

@@ -49,7 +49,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
49 49
         usernameLabel.text = "@\(account.acct)"
50 50
         avatarImageView.image = nil
51 51
         avatarURL = account.avatar
52
-        AvatarCache.shared.get(account.avatar) { image in
52
+        ImageCache.avatars.get(account.avatar) { (image) in
53 53
             DispatchQueue.main.async {
54 54
                 self.avatarImageView.image = image
55 55
                 self.avatarURL = nil
@@ -81,7 +81,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
81 81
     
82 82
     override func prepareForReuse() {
83 83
         if let url = avatarURL {
84
-            AvatarCache.shared.cancel(url)
84
+            ImageCache.avatars.cancel(url)
85 85
         }
86 86
         updateTimestampWorkItem?.cancel()
87 87
         updateTimestampWorkItem = nil

+ 2
- 2
Tusker/Views/Profile Header/ProfileHeaderTableViewCell.swift Ver arquivo

@@ -59,7 +59,7 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
59 59
         
60 60
         avatarImageView.image = nil
61 61
         avatarURL = account.avatar
62
-        AvatarCache.shared.get(account.avatar) { image in
62
+        ImageCache.avatars.get(account.avatar) { (image) in
63 63
             DispatchQueue.main.async {
64 64
                 self.avatarImageView.image = image
65 65
                 self.avatarURL = nil
@@ -89,7 +89,7 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
89 89
     
90 90
     override func prepareForReuse() {
91 91
         if let url = avatarURL {
92
-            AvatarCache.shared.cancel(url)
92
+            ImageCache.avatars.cancel(url)
93 93
         }
94 94
     }
95 95
     

+ 2
- 2
Tusker/Views/Status/ConversationMainStatusTableViewCell.swift Ver arquivo

@@ -77,7 +77,7 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
77 77
         usernameLabel.text = "@\(account.acct)"
78 78
         avatarImageView.image = nil
79 79
         avatarURL = account.avatar
80
-        AvatarCache.shared.get(account.avatar) { image in
80
+        ImageCache.avatars.get(account.avatar) { (image) in
81 81
             DispatchQueue.main.async {
82 82
                 self.avatarImageView.image = image
83 83
                 self.avatarURL = nil
@@ -151,7 +151,7 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
151 151
     
152 152
     override func prepareForReuse() {
153 153
         if let url = avatarURL {
154
-            AvatarCache.shared.cancel(url)
154
+            ImageCache.avatars.cancel(url)
155 155
         }
156 156
         updateTimestampWorkItem?.cancel()
157 157
         updateTimestampWorkItem = nil

+ 2
- 2
Tusker/Views/Status/StatusTableViewCell.swift Ver arquivo

@@ -92,7 +92,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
92 92
         usernameLabel.text = "@\(account.acct)"
93 93
         avatarImageView.image = nil
94 94
         avatarURL = account.avatar
95
-        AvatarCache.shared.get(account.avatar) { image in
95
+        ImageCache.avatars.get(account.avatar) { (image) in
96 96
             DispatchQueue.main.async {
97 97
                 self.avatarImageView.image = image
98 98
                 self.avatarURL = nil
@@ -166,7 +166,7 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
166 166
     
167 167
     override func prepareForReuse() {
168 168
         if let url = avatarURL {
169
-            AvatarCache.shared.cancel(url)
169
+            ImageCache.avatars.cancel(url)
170 170
         }
171 171
         updateTimestampWorkItem?.cancel()
172 172
         updateTimestampWorkItem = nil

Carregando…
Cancelar
Salvar