本篇主要从iOS开发中可以使用到的对大批量图片加载的优化方法进行整理。
将图片加载任务放在后台线程中进行,避免阻塞主线程,这样可以保证应用的响应性和流畅性;
NSOperation和NSOperationQueue是Apple提供的用于多线程处理的框架,可以为每个图片请求创建一个NSBlockOperation,然后添加到NSOperationQueue中;
优点:高度可定制,可以实现复杂的异步操作;
缺点:相对复杂,需要更多的代码和配置。
class AsyncImageLoader: NSObject {
static let shared = AsyncImageLoader()
private let operationQueue = OperationQueue()
func loadImages(withURLs imageURLs: [URL], completion: @escaping ([UIImage]) -> Void) {
if imageURLs.isEmpty {
completion([])
return
}
let operation = BlockOperation {
let loadedImages = self.loadImagesFromURLs(imageURLs)
completion(loadedImages)
}
operation.addDependency(BlockOperation { completion() })
operationQueue.addOperation(operation)
}
private func loadImagesFromURLs(_ imageURLs: [URL]) -> [UIImage] {
var loadedImages = [UIImage]()
var failedImages = [URL]()
for url in imageURLs {
let loadImageOperation = BlockOperation {
if let image = self.loadImage(from: url) {
loadedImages.append(image)
} else {
failedImages.append(url)
}
}
loadImageOperation.completionBlock = {
if failedImages.count > 0 {
print("Failed to load images: \(failedImages)")
}
}
operationQueue.addOperation(loadImageOperation)
}
return loadedImages
}
private func loadImage(from url: URL) -> UIImage? {
// 在这里实现图片加载的逻辑,可以使用 URLSession 来异步加载数据,然后使用 UIImage 来解码图片。
return nil // 返回加载的图片,如果加载失败则返回 nil。
}
}
GCD是Apple提供的一种在iOS和OS X上进行并发编程的解决方案,可以使用dispatch_async在后台线程加载图片;
优点:简单易用,性能良好;
缺点:对于更复杂的并发需求,可能需要更多的代码和配置。
class ImageLoader {
func loadImages(withURLs imageURLs: [URL], completion: @escaping ([UIImage]) -> Void) {
if imageURLs.isEmpty {
completion([])
return
}
let dispatchGroup = DispatchGroup()
var loadedImages = [UIImage]()
var failedImages = [URL]()
for url in imageURLs {
dispatchGroup.enter()
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
failedImages.append(url)
dispatchGroup.leave()
return
}
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode != 200,
let data = data {
failedImages.append(url)
dispatchGroup.leave()
return
} else if let image = UIImage(data: data) {
loadedImages.append(image)
} else {
failedImages.append(url)
}
dispatchGroup.leave()
}.resume()
}
dispatchGroup.notify(queue: .main) {
completion(loadedImages)
if failedImages.count > 0 {
print("Failed to load images: \(failedImages)")
}
}
}
}
这些库处理了异步加载和缓存等,可以非常简单的使用;
优点:简单易用,通常包含缓存机制和优化;
缺点:依赖于第三方库,可能不适合所有项目。
let url = URL(string: "https://example.com/image.jpg") // 替换为您的图片 URL
let resource = ImageResource(downloadURL: url!)
KingfisherManager.shared.retrieveImage(with: resource) { (image, error, cacheType, imageURL) in
if let image = image {
// 在这里处理加载完成的图片
imageView.image = image
} else if let error = error {
// 处理加载错误
print("Error: \(error.localizedDescription)")
}
}
KingfisherManager.shared.cache.storeImage(image, forKey: "myKey", original: resource)
imageView.kf.setImage(with: resource) // 使用 Kingfisher 设置图片
KingfisherManager.shared.cache.clearMemoryCache() // 清空内存缓存
KingfisherManager.shared.cache.clearDiskCache() // 清空磁盘缓存
在Swift5.5引入,可以使用async/await来异步执行代码;
优点:语法简单,易于理解;
缺点:需要Swift5.5+版本。
func loadImage(from url: URL) async -> UIImage? {
let data: Data? = try? await Data.init(contentsOf: url)
if let data = data {
return UIImage(data: data)
}
return nil
}
// 使用示例
let url = URL(string: "https://example.com/image.jpg") // 替换为实际的图片URL
let image = await loadImage(from: url!)
imageView.image = image
常用的优化技术,它只在需要显示图片时才开始加载,而不是一次性加载所有图片,可以减少内存占用和加载时间;
为需要懒加载的图片设置一个属性;
使用didMoveToSuperview或didMoveToWindow方法来检查图片是否可见;
只有当图片显示时才开始加载图片。
SDWebImage或Kingfisher已经内置了懒加载功能;
使用这些库可以简化懒加载实现过程。
imageView.kf.lazy(with: resource).placeholder(with: nil).into(imageView)
创建自定义的ImageVIew的子类,在layoutSubviews方法中检查图片是否可见,只有在图片即将显示时调用setImage方法。
使用响应式编程框架可以声明式方法处理懒加载,使用观察者模式来观察UI元素的状态,并在需要时触发图片加载。
即使使用了懒加载,一次性加载大量图片仍然可能导致内存问题,使用合适的缓存策略,并及时释放不再需要的资源。
在用户滚动视图时,预加载即将显示的图片,可以通过监听滚动事件并提前开始加载图片。
使用Xcode的Instruments工具来监控内存使用和性能瓶颈,调试和优化懒加载实现,确保它在各种情况下都能正常工作。
通过缓存,可以存储已加载的图片,以便需要时快速检索,而不是重新从网络或其他来源加载;
iOS提供了NSURLCache类,可以用于缓存HTTP请求和响应,可以通过配置NSURLCache的内存和磁盘容量以适应应用需求。
private func setupURLCache() {
// 创建一个自定义的URL缓存对象
let cache = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: "myCustomCachePath")
URLCache.shared = cache
}
private func loadImages() {
// 模拟加载大量图片
for i in 1...100 {
let url = URL(string: "https://example.com/image_\(i).jpg")!
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Error loading image: \(error.localizedDescription)")
return
}
if let response = response as? HTTPURLResponse, let data = data {
// 将响应数据缓存到磁盘中
URLCache.shared.storeCachedResponse(response, for: request)
}
}.resume()
}
}
对于更复杂的用例,可以实现自己的缓存策略;
考虑使用键值存储(如UserDefaults或CoreData)或第三方缓存库(Haneke或Flare)。
// 配置Flare
Flare.shared.config = .init(diskCacheDirectory: "flare_cache", diskCacheSizeLimit: 100 * 1024 * 1024) // 100MB
// 使用Flare加载图片并缓存
Flare.shared.load(imageURL: imageURL) { (result) in
switch result {
case .success(let image):
imageView.image = image
case .failure(let error):
print("加载图片失败: \(error.localizedDescription)")
}
}
Flare.shared.clearDiskCache() // 清理磁盘缓存
将图片存储到缓存之前,对其进行适当压缩和优化,可以减少缓存的大小并提高加载速度。
func compressImage(_ image: UIImage, quality: CGFloat = 0.8) -> Data? {
var compressedData: Data?
let imageRef: CGImage = image.cgImage?.cropping(to: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
if let imageRep = CGImageRepresentation(imageRef) {
compressedData = imageRep.jpegData(options: [.compressionFactor: quality])
}
return compressedData
}
定期清理过是或不常用的缓存数据以释放空间并保持缓存的有效性;
可以使用定时任务或后台任务来执行清理操作。
监听设备的网络状态变化,并根据网络条件动态调整图片加载策略;
监测服务器响应时间,以便在必要时进行重试或回退到缓存数据。
在用户离线时提供预先加载和缓存的图片,使用后台任务和后台刷新来预先获取数据。
加载图片时,可以根据需要调整图片的质量(可以通过降低分辨率或压缩图片来减少加载时间和内存占用)。
// 创建一个UIImage对象,并使用缩略图模式加载图片
let image = UIImage(named: "example.jpg")?.generateThumbnail(width: 100)
// 将缩略图设置到UIImageView中
let imageView = UIImageView(image: image)
选择适当的图片格式对于优化图片加载性能和节省存储空间很重要;
特点:广泛使用的格式,支持有损压缩;
适用场景:照片和其他需要高质量细节的图像。
特点:无损压缩,支持透明度和alpha通道;
适用场景:图标、按钮和其他需要透明背景的图像。
特点:支持动画和透明度;
适用场景:动画和循环显示的简单图形。
特点:支持多种压缩算法和颜色模式;
适用场景:需要高质量和多页的文档或图像。
特点:由Google开发的现代格式,支持有损和无损压缩;
适用场景:需要在网页上快速加载的图片,以及需要节省带宽和存储的应用。
特点:高效压缩,支持高分辨率和透明度;
适用场景:iPhone和iPad上的照片,以及其他需要搞笑存储和传输的图像。
特点:类似于GIF的动画格式,但支持更高的图像质量和更小的文件大小;
适用场景:需要展示动画效果的网页和其他应用程序。
特点:基于矢量的格式,可缩放而不失清晰度;
适用场景:需要高分辨率和适应不同屏幕尺寸的图形设计。
支持矢量图形和高质量打印输出,适用于电子文档、表单和需要精确布局的图像。
高质量的原始图像格式,通常用于专业摄影和需要无损质量的场合;
适用场景:需要最大程度保留原始图像数据的情境。
Apple专有的原始图像格式,结合了RAW和JPEFG的优势,适用于iPhone和iPad上的专业摄影;
适用场景:需要高质量、灵活且易于分享的摄影作品。
将图片资源合理组织起来,以方便管理和查找,提高加载效率;
将图片资源按功能或用途分类,放在不同的文件夹中。
为图片文件使用描述性的、易于理解的名称,避免使用随机或过于复杂的文件名。
根据服务器的设置,通过设置请求头和响应头来优化图像的加载和缓存(Cache-Control、ETag)。