Created
March 8, 2023 09:24
-
-
Save ipavlidakis/41a7243388cd1ddb7c853785e08693bf to your computer and use it in GitHub Desktop.
ReusableComponentCache for UITableView and UICollectionView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final class ReusableComponentCache<Element: Hashable> { | |
struct ReusableContainer { | |
let element: Element | |
let invalidationClosure: () -> Void | |
} | |
private var notificationCenterObserver: Any? | |
private var availableElements: Set<Element> = .init() | |
private var elementsInUse: [AnyHashable: Element] = [:] | |
private let lock = NSRecursiveLock() | |
private let elementProvider: () -> Element | |
init( | |
_ elementProvider: @escaping () -> Element | |
) { | |
self.elementProvider = elementProvider | |
notificationCenterObserver = NotificationCenter.default.addObserver( | |
forName: UIApplication.didReceiveMemoryWarningNotification, | |
object: nil, | |
queue: nil | |
) { [weak self] _ in self?.didReceiveMemoryWarning() } | |
} | |
deinit { | |
notificationCenterObserver = nil | |
} | |
func requestReusableElement(_ ownerToken: AnyHashable) -> ReusableContainer { | |
syncExecution { | |
let element: Element | |
if let inUseElement = elementsInUse[ownerToken] { | |
element = inUseElement | |
logMessage("Element already in use for token \(ownerToken)") | |
} else if let firstAvailable = availableElements.first { | |
availableElements.remove(firstAvailable) | |
elementsInUse[ownerToken] = firstAvailable | |
element = firstAvailable | |
logMessage("Reusing element for token \(ownerToken)") | |
} else { | |
element = elementProvider() | |
logMessage("Creating a new element for token \(ownerToken)") | |
} | |
elementsInUse[ownerToken] = element | |
return .init(element: element) { [weak self] in | |
self?.releaseReusableElement(for: ownerToken) | |
} | |
} | |
} | |
private func releaseReusableElement( | |
for ownerToken: AnyHashable | |
) { | |
syncExecution { | |
guard let element = elementsInUse[ownerToken] else { | |
logMessage("No associated element found for token \(ownerToken)") | |
return | |
} | |
elementsInUse[ownerToken] = nil | |
availableElements.insert(element) | |
logMessage("Released element for token \(ownerToken)") | |
} | |
} | |
private func didReceiveMemoryWarning() { | |
syncExecution { | |
availableElements = .init() | |
logMessage("Did receive memory warning and cleaned up") | |
} | |
} | |
private func syncExecution<T>( | |
_ block: () -> T | |
) -> T { | |
lock.lock() | |
defer { lock.unlock() } | |
return block() | |
} | |
private func logMessage(_ message: String) { | |
debugPrint("[ReusableComponentCache]\(message)(availableElements: \(availableElements.count) | inUseElements: \(elementsInUse.count))") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment