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")?.cgImage
return 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 in
switch 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: String
unowned(unsafe) open var delegate: (any NSCacheDelegate)?
open func object(forKey key: KeyType) -> ObjectType?
open func setObject(_ obj: ObjectType, forKey key: KeyType) // 0 cost
open 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 strict
open var countLimit: Int // limits are imprecise/not strict
open 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
休眠时异步加载,异步对图片解码,利用好缓存,在内存警告时及时释放内存,最后渲染时直接渲染。