0 Replies
      Latest reply on Oct 29, 2019 1:11 AM by jeudesprits
      jeudesprits Level 1 Level 1 (0 points)

        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 simple GroupOperation from AdvancedOperation WWDC topic.
        https://gist.github.com/jeudesprits/39c1e97bfda4e7a7b739c2deb50b9c3b 
        https://gist.github.com/jeudesprits/8814279336d38de8167fd2426a63e995


        The 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