要独自处理数据
在列表或许滑动视图中,很大概率 关于大量图片或许文字信息的聚合进行显现。cell 的重用机制,只是是体系帮忙处理的UI 的频频new 的问题。避免了内存的爆增加,然后导致UI上的卡顿,手机发热,耗电 等一系列问题。可是 这个优化只是 只能帮到 UI 请求内存的这个环节。 待处理的问题
- 数据读取频频问题
- 数据不显现 还在进行中耗费内存的问题
- 网络数据,和本地数据 不同的加载
前一篇 SwiftUI to UIKit 之 UICollectionView (UI 篇) 关于数据逻辑 和加载机遇进行简单表述,这一批详细来一波
经过重复的demo测验 最后选了一个计划
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// 数据加载
// 数据行列出队显现成果
}
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
// 数据行列入队
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// 取消数据正在处理的没来得及显现的操作
// 数据行列出队取消
}
细说这个计划
假如是本地图片
关于本地图片基础的操作是 这样
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,这样 就多了个将操作 包装
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
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 飙升 有溃散风险。
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 的时分
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 开发者 处于算法荒漠的状况的一个摆脱
/// 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 的挑战。