What is the purpose of the new .memoryTarget option in CIContextOption added in iOS 17? And it is interesting that this option is added only in swift interface.
https://developer.apple.com/documentation/coreimage/cicontextoption/4172811-memorytarget
Post not yet marked as solved
I use the most usual method to load images:dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, identifier in
switch indexPath.section {
case 0:
return self.configureCell(type: DSCVFeed1CollectionViewCell.self, in: collectionView, for: indexPath) { cell in
guard case let .feed1(model) = identifier else { return }
cell.representedId = model.identifier
cell.configure(with: model)
self.asyncFetcher.fetchAsync(
for: model.identifier,
with: (model.imageUrl, cell.illustrationSize, collectionView.traitCollection.displayScale)
) { image in
DispatchQueue.main.async {
guard cell.representedId == model.identifier else { return }
cell.configure(with: image)
}
}
}
// ...and func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard indexPath.section != 10 else { continue }
guard let item = dataSource.itemIdentifier(for: indexPath) else { continue }
asyncFetcher.fetchAsync(
for: item.identifier,
with: (item.imageUrl, CGSize(width: 100.0, height: 100.0), collectionView.traitCollection.displayScale)
)
}
}
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
guard indexPath.section != 10 else { continue }
guard let item = dataSource.itemIdentifier(for: indexPath) else { continue }
asyncFetcher.cancelFetch(item.identifier)
}
}Also I use simple AsyncImageFetcher(with some custom) class provided by Apple in UICollectionViewDataSourcePrefetching documentation:final class AsyncImageFetcher {
// MARK: - OSLog
private static let log =
ProcessInfo.processInfo.environment.keys.contains("SIGNPOSTS_FOR_ASYNCIMAGEFETCHER")
? OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AsyncImageFetcher")
: .disabled
// MARK: - Queues
private let underlyingQueue: DispatchQueue?
private let fetchQueue: PSOperationQueue
// MARK: - Stores
private var operations = [UUID: GetPreferredImageOperation]()
typealias CompletionHandler = (UIImage) -> Void
private var completionHandlers = [UUID: [CompletionHandler]]()
private let cache = PSCache<uuid, uiimage="">()
// MARK: - DI
typealias Dependency = (URLSession)
let (session): Dependency
// MARK: - Initializers
init(dependency: Dependency = (.shared), underlineQueue: DispatchQueue? = nil) {
(session) = dependency
self.underlyingQueue = underlineQueue
fetchQueue = PSOperationQueue()
fetchQueue.underlyingQueue = underlineQueue
}
// MARK: - Fetch API
typealias Parameters = (url: URL, pointSize: CGSize, scaleFactor: CGFloat)
func fetchAsync(
for identifier: UUID,
with params: Parameters,
completionHandler: CompletionHandler? = nil
) {
if let completionHandler = completionHandler {
let completionHandlers = self.completionHandlers[identifier, default: []]
self.completionHandlers[identifier] = completionHandlers + CollectionOfOne(completionHandler)
}
self.fetchData(for: identifier, with: params)
}
func cancelFetch(_ identifier: UUID) {
//self.fetchQueue.isSuspended = true
//defer { self.fetchQueue.isSuspended = false }
// self.operations[identifier]?.cancel()
// self.completionHandlers[identifier] = nil
}
// MARK: - Private
private func fetchData(for identifier: UUID, with params: Parameters) {
print(operations.count)
guard operations[identifier] == nil else { return }
if let data = cache[identifier] {
invokeCompletionHandlers(for: identifier, with: data)
} else {
let operation =
GetPreferredImageOperation(
identifier: identifier,
underlyingQueue: underlyingQueue,
session: session,
at: params.url,
to: params.pointSize,
withScale: params.scaleFactor
) { [weak self] (preferredImage) in
guard let image = preferredImage else { return }
self?.cache[identifier] = image
self?.invokeCompletionHandlers(for: identifier, with: image)
}
operation.completionBlock = { [weak self, unowned operation] in
DispatchQueue.main.async {
self?.operations[operation.identifier] = nil
}
}
operations[identifier] = operation
fetchQueue.addOperation(operation)
}
}
private func invokeCompletionHandlers(for identifier: UUID, with fetchedData: UIImage) {
let completionHandlers = self.completionHandlers[identifier, default: []]
self.completionHandlers[identifier] = nil
for completionHandler in completionHandlers { completionHandler(fetchedData) }
}
}GetPreferredImageOperation is a simpleGroupOperationfrom AdvancedOperation WWDC topic.https://gist.github.com/jeudesprits/39c1e97bfda4e7a7b739c2deb50b9c3b https://gist.github.com/jeudesprits/8814279336d38de8167fd2426a63e995The problem is that without canceling operations, everything works much faster and creates much fewer threads. Although it should be exactly the opposite. What could be the matter? All OperationQueue`s use a same private serial queue as underlyingQueue.Without cancelling:8i3t26.jpg With cancelation:LfeKZa.jpg