Skip to content

Instantly share code, notes, and snippets.

@ipavlidakis
Created March 8, 2023 09:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ipavlidakis/41a7243388cd1ddb7c853785e08693bf to your computer and use it in GitHub Desktop.
Save ipavlidakis/41a7243388cd1ddb7c853785e08693bf to your computer and use it in GitHub Desktop.
ReusableComponentCache for UITableView and UICollectionView
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