SwiftUI to UIKit 之 UICollectionView (数据 篇)

要单独处理数据

在列表或者滑动视图中,很大概率 对于大量图片或者文字信息的聚合进行显示。cell 的重用机制,仅仅是系统帮忙处理的UI 的频繁new 的问题。避免了内存的爆增加,从而导致UI上的卡顿,手机发热,耗电 等一系列问题。但是 这个优化仅仅 只能帮到 UI 申请内存的这个环节。 待解决的问题

  1. 数据读取频繁问题
  2. 数据不显示 还在进行中消耗内存的问题
  3. 网络数据,和本地数据 不同的加载

前一篇 SwiftUI to UIKit 之 UICollectionView (UI 篇) 对于数据逻辑 和加载时机进行简单表述,这一批详细来一波

经过反复的demo测试 最后选了一个方案

swift 复制代码
        func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
	// 数据加载
	// 数据队列出队显示结果
}
swift 复制代码
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
	// 数据队列入队
}
swift 复制代码
        func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
	// 取消数据正在处理的没来得及显示的操作
	// 数据队列出队取消
}

细说这个方案

如果是本地图片

对于本地图片基础的操作是 这样

less 复制代码
 public func getThumbnail(for asset: PHAsset, size: CGSize, completion: @escaping (UIImage?) -> Void) {
        let options = PHImageRequestOptions()
        options.resizeMode = .none
        options.deliveryMode = .highQualityFormat
        PHCachingImageManager.default().requestImage(
            for: asset,
            targetSize: size,
            contentMode: .aspectFill,
            options: options) { (image, _) in
            completion(image)
        }
    }

通过 PHAsset 这个Photo库中的Class 来提取手机相簿里的照片数据,但是,如果刚刚拍摄完成的图片,会有一定概率的读取缓慢的情况。 这又来一个问题,如何空着读取的操作,控制的方法有很多,开始我想的是用OC 里GCD ,但是GCD语法和封装不方便其他地方使用,这个时候 换了个角度用NSOperation,这样 就多了个将操作 包装

swift 复制代码
import UIKit
import PhotosUI

internal class DataLoadOperation: Operation {
    // MARK: - Public properties
    var image: UIImage?
    var loadingCompleteHandler: ((UIImage?) -> Void)?
    
    // MARK: - Properties
    private var _asset: PHAsset
    private var _size: CGSize
    
    override var isAsynchronous: Bool {
        return true
    }
    
    // MARK: - Methods
    init(_ asset: PHAsset, size: CGSize) {
        _asset = asset
        _size = size
    }
    
    override func main() {
        if isCancelled { return }
        
        let manager = PhotoLibManager()
        manager.getThumbnail(for: _asset, size: _size) { image in
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                if self.isCancelled { return }
                self.image = image ?? UIImage()
                self.loadingCompleteHandler?(self.image)
            }
        }
    }
    
    // MARK: - Static methods
    public static func getImage(for asset: PHAsset, size: CGSize) -> DataLoadOperation? {
        return DataLoadOperation(asset, size: size)
    }
}

利用Operation 的 开始 暂停 取消等一些列开放API

swift 复制代码
open class Operation : NSObject, @unchecked Sendable {

    
    open func start()

    open func main()

    
    open var isCancelled: Bool { get }

    open func cancel()

    
    open var isExecuting: Bool { get }

    open var isFinished: Bool { get }

    open var isConcurrent: Bool { get }

    @available(iOS 7.0, *)
    open var isAsynchronous: Bool { get }

    open var isReady: Bool { get }

    
    open func addDependency(_ op: Operation)

    open func removeDependency(_ op: Operation)
	............
	............

下一步 就是 调用的 add 和remove 的Operation 的时机,cell 的willDisplay 和 didEndDisplaying 的时候。之前我是在prefetchItemsAt 和 cancelPrefetchingForItemsAt 这样的PreFetchDelelgate 代理方法里面,效果没有这样的好。主要还是 PreFetch Delegate 返回的数据量在列数过多的情况下,返回的indexPath 过多,cpu 飙升 有崩溃风险。

swift 复制代码
 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {

let updateCellClosure: (UIImage?) -> Void = { [unowned self] image in
                    cell.setThumbnail(image, data: p_item)
                    self.loadingOperations.removeValue(forKey: indexPath)
                }
                if let dataLoader = loadingOperations[indexPath] {
                    if let image = dataLoader.image {
                        cell.setThumbnail(image, data: p_item)
                        loadingOperations.removeValue(forKey: indexPath)
                    } else {
                        dataLoader.loadingCompleteHandler = updateCellClosure
                    }
                } else {
                    let size = CGSize(width:  UIScreen.main.bounds.size.width / CGFloat(self.parent.changeUI.numColum), height:  UIScreen.main.bounds.size.width / CGFloat(self.parent.changeUI.numColum))
                    guard let asset = p_item.phAsset else { return }
                    if let dataLoader = DataLoadOperation.getImage(for: asset, size: size) {
                        dataLoader.loadingCompleteHandler = updateCellClosure
                        loadingQueue.addOperation(dataLoader)
                        loadingOperations[indexPath] = dataLoader
                    }
                }
}

在DisDisPlay 的时候

swift 复制代码
  func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
            // Cancel pending data load operations when the data is no longer required.
            if let dataLoader = loadingOperations[indexPath] {
                dataLoader.cancel()
                loadingOperations.removeValue(forKey: indexPath)
            }
            cancelFetchPhoto(ofIndex: indexPath)
        }


   func cancelFetchPhoto(ofIndex indexPath: IndexPath) {
            if let dataLoader = loadingOperations[indexPath] {
                dataLoader.cancel()
                loadingOperations.removeValue(forKey: indexPath)
            } else {
                if let url =  kfRequestURLs[indexPath] {
                    ImagePrefetcher(urls: [url]).stop()
                }
            }
        }

这里的else 是网络的图片 通过KF 来进行管理,这就是另外的逻辑的。但是调用的时机 是一样的。

网络图片的处理

KingFisher 对于图片的预处理 有自己的逻辑和处理API ImagePrefetcher 就是其中之一,如果开发者对于这个感觉不行,可以参考本地图片处理 来自己写一个,这个可以加入自己对于项目的理解来添加自己的感觉不错性能的算法。 特别是index 的查找问题,数据缓存 存储问题 等,都可以加入自己感觉不错的开源算法,也是提高自己的一个途径,特别是对于 iOS 开发者 处于算法荒漠的情况的一个摆脱

less 复制代码
/// will display 

  guard let URL = p_item.thumbnail else { return }
                kfRequestURLs[indexPath] = URL
                let didModifier:AnyModifier =  SKPhotoTool.makeHeadOfImage(p_item)
                cell.img.kf.setImage(with: URL, placeholder:UIImage(named: "default_icon"), options: [.downloadPriority(0.5),.requestModifier(didModifier)])


/// cancel 

 ImagePrefetcher(urls: [url]).stop()

这样对于快速滑动的 视图 的了里面图片 有 了很好的处理内存问题,再在切换,退出 进入 等入口,进行 内存的清理。 通过测试 3w张图片 没有出现卡顿和崩溃的问题,当然手机是iPhone12 ,如果是一台iPhone6 页保证不了了。

小结 :

这个文章写了两篇,可能不会有太多的人关注因为这里仅仅一个问题

UICollection 快速滑动问题,和SwiftUI 嵌入UIKit的问题

但是这个路线其实不是原生开发的选择,是苹果逼着开发者必须走的路线。

因为 LazyVGrid 和List 目前还不能保证 企业 产品的 需求,开发Demo 可能无感,感觉SwiftUI 语法和结构更直观。

但是一旦数据和业务复杂起来,LazyVGrid 的下拉和上拉就要写半天,滑动选中更是要 调试算法了,最后导致一个项目各种地方的下拉和上拉的处理逻辑和方式不一样。 有时候不知道这个是退步还是进步,5分钟 的事情 变成了LazyGrid 的挑战。

相关推荐
Leyla9 分钟前
【代码重构】好的重构与坏的重构
前端
影子落人间12 分钟前
已解决npm ERR! request to https://registry.npm.taobao.org/@vant%2farea-data failed
前端·npm·node.js
世俗ˊ36 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92136 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_41 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css