iOS开发进阶之列表加载图片
列表加载图片通常使用UITableView
或UICollectionView
,由于列表中内容数量不确定并且对于图片质量要求也不确定,所以对于图片加载的优化是很有必要的。
首先借鉴前文,我们逐步进行操作,以下是加载1000张图片的列表。
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {return 1000}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)cell.contentView.layer.contents = UIImage(named: "iOS")?.cgImagereturn cell}
可以看到在使用复用机制后,我们的内存占用还比较低,我们接下来换网络图片。
对应代码调整,以下。
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)let vi = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: 150, height: 100)))vi.kf.setImage(with: URL(string: "https://img0.baidu.com/it/u=245753553,2056265008&fm=253&fmt=auto?w=1280&h=800")!)cell.contentView.addSubview(vi)return cell}
此时的网络图片对应尺寸会比较大,在滚动时可以明显感受到卡顿,接下来换一下加载方式,以下。
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)DispatchQueue.global(qos: .background).async {let url = "https://img0.baidu.com/it/u=245753553,2056265008&fm=253&fmt=auto?w=1280&h=800"ImageDownloader.default.downloadImage(with: URL(string: url)!, options: .none) { result inswitch result{case let .success(value):UIGraphicsBeginImageContextWithOptions(CGSize(width: 150, height: 100), true, 0)value.image.draw(in: CGRect(origin: .zero, size: CGSize(width: 150, height: 100)))let image = UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()DispatchQueue.main.async {cell.contentView.layer.contents = image?.cgImage}default:break}}}return cell}
以上使用了自己的方式进行图片解码,之所以如此是因为在如果直接加载图片在iOS中就会强制图片解码,相对而言图片解码相当耗时,现在我们将图片解码放在异步延迟处理,然后等解码完成后再进行加载,这样的好处就是不会阻塞主线程。
如果我们加载的图片更大,超过了一整屏幕,这时候我们可以使用CATiledLayer
,这是因为它做到了异步的按块的渲染。
先来看看它的注释,以下。
/* This is a subclass of CALayer providing a way to asynchronously* provide tiles of the layer's content, potentially cached at multiple* levels of detail.** As more data is required by the renderer, the layer's* -drawInContext: method is called on one or more background threads* to supply the drawing operations to fill in one tile of data. The* clip bounds and CTM of the drawing context can be used to determine* the bounds and resolution of the tile being requested.** Regions of the layer may be invalidated using the usual* -setNeedsDisplayInRect: method. However update will be asynchronous,* i.e. the next display update will most likely not contain the* changes, but a future update will.** Note: do not attempt to directly modify the `contents' property of* an CATiledLayer object - doing so will effectively turn it into a* regular CALayer. */@available(iOS 2.0, *)
open class CATiledLayer : CALayer
-drawInContext: method is called on one or more background threads to supply the drawing operations to fill in one tile of data.
可以在多个后台线程上调用进行绘图。
-setNeedsDisplayInRect: method. However update will be asynchronous, i.e. the next display update will most likely not contain the changes, but a future update will.
不仅异步还可以做到局部渲染。
那么除此之外,还有其他的手段进行优化吗?
我们来看看NSCache
,以下。
@available(iOS 4.0, *)
open class NSCache<KeyType, ObjectType> : NSObject where KeyType : AnyObject, ObjectType : AnyObject {open var name: Stringunowned(unsafe) open var delegate: (any NSCacheDelegate)?open func object(forKey key: KeyType) -> ObjectType?open func setObject(_ obj: ObjectType, forKey key: KeyType) // 0 costopen func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int)open func removeObject(forKey key: KeyType)open func removeAllObjects()open var totalCostLimit: Int // limits are imprecise/not strictopen var countLimit: Int // limits are imprecise/not strictopen var evictsObjectsWithDiscardedContent: Bool
}@available(*, unavailable)
extension NSCache : @unchecked Sendable {
}public protocol NSCacheDelegate : NSObjectProtocol {@available(iOS 4.0, *)optional func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any)
}
还可以将图片加入缓存,然后在需要使用的时候直接从缓存中取出,NSCache
可以自动进行缓存管理,它做了func didReceiveMemoryWarning()
的应对处理。
图片的加载优化目的是利用更多空闲资源和时间,在激活时更迅速的执行加载过程,比如在列表停止滚动时或者RunLoop
休眠时异步加载,异步对图片解码,利用好缓存,在内存警告时及时释放内存,最后渲染时直接渲染。