Files
divkit/client/ios/DivKit/Views/DivViewPreloader.swift
nshapovalov01 ebf720f789 Fix memory leaks in core components
commit_hash:1f841204f9d5484c75e3f92c9a1faa0e127a58de
2026-05-26 14:55:56 +03:00

145 lines
4.5 KiB
Swift

#if os(iOS)
import Foundation
import VGSL
/// Utility for preloading the contents of `DivView'.
///
/// ``DivViewPreloader`` can be useful when it is important to calculate layout before building
/// ``DivView``, for example in `UICollectionView`.
@MainActor
public final class DivViewPreloader {
/// Represents a change in estimated size for a specific ``DivView``.
public struct DivViewSizeChange {
public let cardId: DivCardID
public let estimatedSize: DivViewSize
}
private(set) var setSourceTask: Task<Void, Error>?
private var blockProviders = [DivCardID: DivBlockProvider]()
private let divKitComponents: DivKitComponents
private let changeEventsPipe = SignalPipe<DivViewSizeChange>()
var changeEvents: Signal<DivViewSizeChange> {
changeEventsPipe.signal
}
/// Initializes a new ``DivViewPreloader`` instance.
///
/// - Parameters:
/// - divKitComponents: The ``DivKitComponents`` instance used for creating `DivBlockProvider`.
public init(divKitComponents: DivKitComponents) {
self.divKitComponents = divKitComponents
}
/// Sets the source for ``DivViewPreloader`` and updates the layout.
/// - Parameters:
/// - source: The source of the ``DivView``.
/// - debugParams: Optional debug configurations for the ``DivView``.
public func setSource(
_ source: DivViewSource,
debugParams: DebugParams = DebugParams()
) async {
setSourceTask = Task { [oldTask = setSourceTask] in
try? await oldTask?.value
try Task.checkCancellation()
let blockProvider: DivBlockProvider = blockProvider(for: source.id.cardId)
await blockProvider.setSource(
source,
debugParams: debugParams
)
blockProviders[source.id.cardId] = blockProvider
}
try? await setSourceTask?.value
}
/// Sets the source for ``DivViewPreloader`` and updates the layout.
/// - Parameters:
/// - source: The source of the ``DivView``.
/// - debugParams: Optional debug configurations for the ``DivView``.
@_spi(Legacy)
public func setSource(
_ source: DivViewSource,
debugParams: DebugParams = DebugParams()
) {
let blockProvider: DivBlockProvider = blockProvider(for: source.id.cardId)
blockProvider.setSource(
source,
debugParams: debugParams
)
blockProviders[source.id.cardId] = blockProvider
}
/// Sets the sources for ``DivViewPreloader`` and updates the layout.
/// - Parameters:
/// - sources: The sources of the ``DivView``.
/// - debugParams: Optional debug configurations for the ``DivView``.
public func setSources(
_ sources: [DivViewSource],
debugParams: DebugParams = DebugParams()
) async {
setSourceTask = Task { [oldTask = setSourceTask] in
try? await oldTask?.value
let blockProviders = sources.map { blockProvider(for: $0.id.cardId) }
await withTaskGroup(of: Void.self) { group in
for (blockProvider, source) in zip(blockProviders, sources) {
group.addTask {
await blockProvider.setSource(source, debugParams: debugParams)
}
}
}
}
try? await setSourceTask?.value
}
/// Fetches the expected size for a ``DivView`` with a specific identifier.
///
/// - Parameters:
/// - cardId: The unique identifier of the desired ``DivView``.
///
/// - Returns: An optional `DivViewSize` representing the expected size if available, otherwise
/// nil.
@MainActor
public func expectedSize(for cardId: DivCardID) -> DivViewSize? {
blockProvider(for: cardId).cardSize
}
/// Adds an observer to listen for any ``DivView`` estimated size changes.
///
/// - Parameters:
/// - onCardSizeChanged: A closure that gets invoked whenever a ``DivView`` estimated size
/// changes.
///
/// - Returns: A `Disposable` which can be used to unregister the observer when it's no longer
/// needed.
public func addObserver(_ onCardSizeChanged: @escaping (DivViewSizeChange) -> Void)
-> Disposable {
changeEvents.addObserver(onCardSizeChanged)
}
func blockProvider(for cardId: DivCardID) -> DivBlockProvider {
if let blockProvider = blockProviders[cardId] {
return blockProvider
} else {
let blockProvider = DivBlockProvider(divKitComponents: divKitComponents) { [weak self] in
self?.changeEventsPipe.send(DivViewSizeChange(cardId: $0, estimatedSize: $1))
}
blockProviders[cardId] = blockProvider
return blockProvider
}
}
public func reset(cardId: DivCardID) {
blockProviders.removeValue(forKey: cardId)
}
public func resetAll() {
blockProviders.removeAll()
}
}
#endif