forked from shadowfacts/Tusker
Use MultiThreadedDictionary for ImageCache request groups
Prevents a crash due a race condition if multiple requets complete simultaneously and attempt to modify the dictionary
This commit is contained in:
parent
a805da9faa
commit
3ff9fdabdb
|
@ -140,7 +140,7 @@
|
||||||
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC19123C271D9000D0238 /* MastodonActivity.swift */; };
|
D64BC19223C271D9000D0238 /* MastodonActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC19123C271D9000D0238 /* MastodonActivity.swift */; };
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* CachedDictionary.swift */; };
|
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */; };
|
||||||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64F80E1215875CC00BEF393 /* XCBActionType.swift */; };
|
||||||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
||||||
|
@ -471,7 +471,7 @@
|
||||||
D64BC19123C271D9000D0238 /* MastodonActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonActivity.swift; sourceTree = "<group>"; };
|
D64BC19123C271D9000D0238 /* MastodonActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonActivity.swift; sourceTree = "<group>"; };
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||||
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedDictionary.swift; sourceTree = "<group>"; };
|
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = "<group>"; };
|
||||||
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
D64F80E1215875CC00BEF393 /* XCBActionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBActionType.swift; sourceTree = "<group>"; };
|
||||||
D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentView.swift; sourceTree = "<group>"; };
|
D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentView.swift; sourceTree = "<group>"; };
|
||||||
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentViewController.swift; sourceTree = "<group>"; };
|
D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvAttachmentViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1360,7 +1360,7 @@
|
||||||
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
D6945C2E23AC47C3005C403C /* SavedDataManager.swift */,
|
||||||
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */,
|
||||||
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */,
|
||||||
D64D8CA82463B494006B0BAA /* CachedDictionary.swift */,
|
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */,
|
||||||
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */,
|
||||||
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
||||||
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
||||||
|
@ -1928,7 +1928,7 @@
|
||||||
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
D641C773213CAA25004B4513 /* NotificationsTableViewController.swift in Sources */,
|
||||||
D6412B0524B0227D00F5412E /* ProfileViewController.swift in Sources */,
|
D6412B0524B0227D00F5412E /* ProfileViewController.swift in Sources */,
|
||||||
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */,
|
||||||
D64D8CA92463B494006B0BAA /* CachedDictionary.swift in Sources */,
|
D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */,
|
||||||
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
|
||||||
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
|
||||||
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */,
|
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */,
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
//
|
|
||||||
// CachedDictionary.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 5/6/20.
|
|
||||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
class CachedDictionary<Value> {
|
|
||||||
private let name: String
|
|
||||||
private var dict = [String: Value]()
|
|
||||||
private let queue: DispatchQueue
|
|
||||||
|
|
||||||
init(name: String) {
|
|
||||||
self.name = name
|
|
||||||
self.queue = DispatchQueue(label: "CachedDictionary (\(name)) Coordinator", attributes: .concurrent)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(key: String) -> Value? {
|
|
||||||
get {
|
|
||||||
var result: Value? = nil
|
|
||||||
queue.sync {
|
|
||||||
result = dict[key]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
queue.async(flags: .barrier) {
|
|
||||||
self.dict[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,7 @@ class ImageCache {
|
||||||
|
|
||||||
private let cache: Cache<Data>
|
private let cache: Cache<Data>
|
||||||
|
|
||||||
private var groups = [URL: RequestGroup]()
|
private var groups = MultiThreadDictionary<URL, RequestGroup>(name: "ImageCache request groups")
|
||||||
|
|
||||||
init(name: String, memoryExpiry expiry: Expiry) {
|
init(name: String, memoryExpiry expiry: Expiry) {
|
||||||
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
let storage = MemoryStorage<Data>(config: MemoryConfig(expiry: expiry))
|
||||||
|
@ -56,7 +56,7 @@ class ImageCache {
|
||||||
if let data = data {
|
if let data = data {
|
||||||
try? self.cache.setObject(data, forKey: key)
|
try? self.cache.setObject(data, forKey: key)
|
||||||
}
|
}
|
||||||
self.groups.removeValue(forKey: url)
|
self.groups.removeValueWithoutReturning(forKey: url)
|
||||||
}
|
}
|
||||||
groups[url] = group
|
groups[url] = group
|
||||||
let request = group.addCallback(completion)
|
let request = group.addCallback(completion)
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// MultiThreadDictionary.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/6/20.
|
||||||
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class MultiThreadDictionary<Key: Hashable, Value> {
|
||||||
|
private let name: String
|
||||||
|
private var dict = [Key: Value]()
|
||||||
|
private let queue: DispatchQueue
|
||||||
|
|
||||||
|
init(name: String) {
|
||||||
|
self.name = name
|
||||||
|
self.queue = DispatchQueue(label: "MultiThreadDictionary (\(name)) Coordinator", attributes: .concurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(key: Key) -> Value? {
|
||||||
|
get {
|
||||||
|
var result: Value? = nil
|
||||||
|
queue.sync {
|
||||||
|
result = dict[key]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
queue.async(flags: .barrier) {
|
||||||
|
self.dict[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeValueWithoutReturning(forKey key: Key) {
|
||||||
|
queue.async(flags: .barrier) {
|
||||||
|
self.dict.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the result of this function is unused, it is preferable to use `removeValueWithoutReturning` as it executes asynchronously and doesn't block the calling thread.
|
||||||
|
func removeValue(forKey key: Key) -> Value? {
|
||||||
|
var value: Value? = nil
|
||||||
|
queue.sync(flags: .barrier) {
|
||||||
|
value = dict.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ struct AccountDisplayNameLabel<Account: AccountProtocol>: View {
|
||||||
let matches = emojiRegex.matches(in: account.displayName, options: [], range: fullRange)
|
let matches = emojiRegex.matches(in: account.displayName, options: [], range: fullRange)
|
||||||
guard !matches.isEmpty else { return }
|
guard !matches.isEmpty else { return }
|
||||||
|
|
||||||
let emojiImages = CachedDictionary<Image>(name: "AcccountDisplayNameLabel Emoji Images")
|
let emojiImages = MultiThreadDictionary<String, Image>(name: "AcccountDisplayNameLabel Emoji Images")
|
||||||
|
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class ContentTextView: LinkTextView {
|
||||||
func setEmojis(_ emojis: [Emoji]) {
|
func setEmojis(_ emojis: [Emoji]) {
|
||||||
guard !emojis.isEmpty else { return }
|
guard !emojis.isEmpty else { return }
|
||||||
|
|
||||||
let emojiImages = CachedDictionary<UIImage>(name: "ContentTextView Emoji Images")
|
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "ContentTextView Emoji Images")
|
||||||
|
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class EmojiLabel: UILabel {
|
||||||
let matches = emojiRegex.matches(in: attributedText.string, options: [], range: attributedText.fullRange)
|
let matches = emojiRegex.matches(in: attributedText.string, options: [], range: attributedText.fullRange)
|
||||||
guard !matches.isEmpty else { return }
|
guard !matches.isEmpty else { return }
|
||||||
|
|
||||||
let emojiImages = CachedDictionary<UIImage>(name: "EmojiLabel Emoji Images")
|
let emojiImages = MultiThreadDictionary<String, UIImage>(name: "EmojiLabel Emoji Images")
|
||||||
|
|
||||||
let group = DispatchGroup()
|
let group = DispatchGroup()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue