|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
在iOS应用开发中,图片是UI设计中不可或缺的元素。然而,不恰当的图片处理方式可能导致内存占用过高、界面卡顿,甚至应用崩溃。本文将全面介绍如何在Swift中高效管理UIImage的尺寸,从而优化内存使用,解决性能问题,为用户提供流畅的体验。
UIImage基础
UIImage是iOS开发中用于表示图像的核心类。了解UIImage的基本特性对于有效管理图片尺寸至关重要。
- // 创建一个简单的UIImage实例
- let image = UIImage(named: "example_image")
复制代码
每个UIImage对象都包含图像数据以及与其关联的元数据,如尺寸、缩放比例和方向。以下是UIImage的一些关键属性:
- if let image = UIImage(named: "example_image") {
- // 图片的尺寸(以点为单位)
- let size = image.size
- print("Image size: \(size.width) x \(size.height)")
-
- // 图片的缩放比例
- let scale = image.scale
- print("Image scale: \(scale)")
-
- // 图片的实际像素尺寸
- let pixelSize = CGSize(width: size.width * scale, height: size.height * scale)
- print("Image pixel size: \(pixelSize.width) x \(pixelSize.height)")
-
- // 图片的方向
- let orientation = image.imageOrientation
- print("Image orientation: \(orientation.rawValue)")
- }
复制代码
获取图片尺寸
获取图片尺寸是图片处理的第一步。Swift提供了多种方式来获取图片尺寸信息。
从资源文件获取图片尺寸
- // 方法1:直接创建UIImage并获取尺寸
- if let image = UIImage(named: "example_image") {
- let size = image.size
- print("Image size: \(size.width) x \(size.height)")
- }
- // 方法2:通过UIImageAsset获取(适用于多分辨率图片)
- if let asset = UIImageAsset(named: "example_image") {
- let image = asset.image(with: UITraitCollection(displayScale: UIScreen.main.scale))
- let size = image.size
- print("Image size: \(size.width) x \(size.height)")
- }
复制代码
从文件URL获取图片尺寸
- func imageSize(at url: URL) -> CGSize? {
- // 使用CGImageSource获取图片信息而不完全加载图片
- guard let source = CGImageSourceCreateWithURL(url, nil),
- let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] else {
- return nil
- }
-
- // 获取像素尺寸
- let pixelWidth = metadata[kCGImagePropertyPixelWidth as String] as? CGFloat ?? 0
- let pixelHeight = metadata[kCGImagePropertyPixelHeight as String] as? CGFloat ?? 0
-
- // 获取图片的DPI(用于计算逻辑尺寸)
- let dpiWidth = metadata[kCGImagePropertyDPIWidth as String] as? CGFloat ?? 72
- let dpiHeight = metadata[kCGImagePropertyDPIHeight as String] as? CGFloat ?? 72
-
- // 计算逻辑尺寸(点)
- let pointWidth = pixelWidth * 72 / dpiWidth
- let pointHeight = pixelHeight * 72 / dpiHeight
-
- return CGSize(width: pointWidth, height: pointHeight)
- }
- // 使用示例
- if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
- let imageURL = documentsURL.appendingPathComponent("example.jpg")
- if let size = imageSize(at: imageURL) {
- print("Image size: \(size.width) x \(size.height)")
- }
- }
复制代码
从网络获取图片尺寸
- import ImageIO
- func getImageSizeFromURL(_ url: URL, completion: @escaping (CGSize?) -> Void) {
- let task = URLSession.shared.dataTask(with: url) { data, response, error in
- guard let data = data, error == nil else {
- completion(nil)
- return
- }
-
- // 使用CGImageSource获取图片信息而不完全解码图片
- if let source = CGImageSourceCreateWithData(data as CFData, nil),
- let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] {
-
- let pixelWidth = metadata[kCGImagePropertyPixelWidth as String] as? CGFloat ?? 0
- let pixelHeight = metadata[kCGImagePropertyPixelHeight as String] as? CGFloat ?? 0
-
- DispatchQueue.main.async {
- completion(CGSize(width: pixelWidth, height: pixelHeight))
- }
- } else {
- DispatchQueue.main.async {
- completion(nil)
- }
- }
- }
- task.resume()
- }
- // 使用示例
- if let url = URL(string: "https://example.com/image.jpg") {
- getImageSizeFromURL(url) { size in
- if let size = size {
- print("Image size: \(size.width) x \(size.height)")
- } else {
- print("Failed to get image size")
- }
- }
- }
复制代码
图片尺寸调整
调整图片尺寸是优化内存使用和提升性能的关键步骤。以下是几种常见的图片尺寸调整方法。
简单的尺寸调整
- extension UIImage {
- func resized(to size: CGSize) -> UIImage? {
- UIGraphicsBeginImageContextWithOptions(size, false, scale)
- defer { UIGraphicsEndImageContext() }
-
- draw(in: CGRect(origin: .zero, size: size))
- return UIGraphicsGetImageFromCurrentImageContext()
- }
-
- func resized(toWidth width: CGFloat) -> UIImage? {
- let height = width * (size.height / size.width)
- return resized(to: CGSize(width: width, height: height))
- }
-
- func resized(toHeight height: CGFloat) -> UIImage? {
- let width = height * (size.width / size.height)
- return resized(to: CGSize(width: width, height: height))
- }
- }
- // 使用示例
- if let originalImage = UIImage(named: "large_image") {
- // 调整到指定尺寸
- if let resizedImage = originalImage.resized(to: CGSize(width: 200, height: 200)) {
- // 使用调整后的图片
- }
-
- // 按宽度调整,保持宽高比
- if let widthResizedImage = originalImage.resized(toWidth: 300) {
- // 使用调整后的图片
- }
-
- // 按高度调整,保持宽高比
- if let heightResizedImage = originalImage.resized(toHeight: 300) {
- // 使用调整后的图片
- }
- }
复制代码
高质量的尺寸调整
- extension UIImage {
- func highQualityResized(to size: CGSize) -> UIImage? {
- let renderer = UIGraphicsImageRenderer(size: size)
- return renderer.image { _ in
- self.draw(in: CGRect(origin: .zero, size: size))
- }
- }
-
- func highQualityResized(toWidth width: CGFloat) -> UIImage? {
- let height = width * (size.height / size.width)
- return highQualityResized(to: CGSize(width: width, height: height))
- }
-
- func highQualityResized(toHeight height: CGFloat) -> UIImage? {
- let width = height * (size.width / size.height)
- return highQualityResized(to: CGSize(width: width, height: height))
- }
- }
- // 使用示例
- if let originalImage = UIImage(named: "large_image") {
- if let highQualityResizedImage = originalImage.highQualityResized(toWidth: 300) {
- // 使用高质量调整后的图片
- }
- }
复制代码
使用Core Image进行高级调整
- import CoreImage
- import CoreImage.CIFilterBuiltins
- extension UIImage {
- func resizedUsingCoreImage(to size: CGSize) -> UIImage? {
- guard let cgImage = self.cgImage else { return nil }
-
- let ciImage = CIImage(cgImage: cgImage)
- let context = CIContext()
-
- // 计算缩放比例
- let scaleX = size.width / self.size.width
- let scaleY = size.height / self.size.height
-
- // 创建变换并应用缩放
- let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
- let scaledImage = ciImage.transformed(by: transform)
-
- // 渲染结果
- if let outputCGImage = context.createCGImage(scaledImage, from: scaledImage.extent) {
- return UIImage(cgImage: outputCGImage, scale: self.scale, orientation: self.imageOrientation)
- }
-
- return nil
- }
-
- func resizedUsingCoreImage(toWidth width: CGFloat) -> UIImage? {
- let height = width * (size.height / size.width)
- return resizedUsingCoreImage(to: CGSize(width: width, height: height))
- }
- }
- // 使用示例
- if let originalImage = UIImage(named: "large_image") {
- if let coreImageResized = originalImage.resizedUsingCoreImage(toWidth: 300) {
- // 使用Core Image调整后的图片
- }
- }
复制代码
使用vImage进行高性能调整
- import Accelerate
- extension UIImage {
- func resizedUsingvImage(to size: CGSize) -> UIImage? {
- guard let cgImage = self.cgImage else { return nil }
-
- var sourceBuffer = vImage_Buffer()
- defer {
- if sourceBuffer.data != nil {
- free(sourceBuffer.data)
- }
- }
-
- var destinationBuffer = vImage_Buffer()
- defer {
- if destinationBuffer.data != nil {
- free(destinationBuffer.data)
- }
- }
-
- var format = vImage_CGImageFormat(bitsPerComponent: 8,
- bitsPerPixel: 32,
- colorSpace: CGColorSpaceCreateDeviceRGB(),
- bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
- version: 0,
- decode: nil,
- renderingIntent: .defaultIntent)
-
- var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, .noFlags)
- guard error == kvImageNoError else { return nil }
-
- error = vImageBuffer_Init(&destinationBuffer, vImagePixelCount(size.height), vImagePixelCount(size.width), format.bitsPerPixel, .noFlags)
- guard error == kvImageNoError else { return nil }
-
- // 执行缩放
- error = vImageScale_ARGB8888(&sourceBuffer, &destinationBuffer, nil, .noFlags)
- guard error == kvImageNoError else { return nil }
-
- // 创建输出图像
- guard let resizedCGImage = vImageCreateCGImageFromBuffer(&destinationBuffer, &format, nil, nil, .noFlags, &error)?.takeRetainedValue(),
- error == kvImageNoError else {
- return nil
- }
-
- return UIImage(cgImage: resizedCGImage, scale: self.scale, orientation: self.imageOrientation)
- }
- }
- // 使用示例
- if let originalImage = UIImage(named: "large_image") {
- if let vImageResized = originalImage.resizedUsingvImage(to: CGSize(width: 300, height: 300)) {
- // 使用vImage调整后的图片
- }
- }
复制代码
内存优化
优化UIImage的内存占用对于防止应用崩溃和提升性能至关重要。以下是几种优化内存使用的方法。
计算图片内存占用
- extension UIImage {
- var estimatedMemoryUsage: Int {
- // 计算图片的字节大小
- let bytesPerPixel = 4 // 假设每个像素使用4字节(RGBA)
- let widthInPixels = Int(size.width * scale)
- let heightInPixels = Int(size.height * scale)
- return widthInPixels * heightInPixels * bytesPerPixel
- }
- }
- // 使用示例
- if let image = UIImage(named: "large_image") {
- let memoryUsage = image.estimatedMemoryUsage
- print("Estimated memory usage: \(memoryUsage / (1024 * 1024)) MB")
- }
复制代码
使用适当格式的图片
- // PNG格式 - 适合带有透明度的图片
- func loadPNGImage(named name: String) -> UIImage? {
- return UIImage(named: name)
- }
- // JPEG格式 - 适合照片类图片,可以压缩
- func loadJPEGImage(named name: String, compressionQuality: CGFloat = 0.8) -> UIImage? {
- guard let imagePath = Bundle.main.path(forResource: name, ofType: "jpg"),
- let imageData = try? Data(contentsOf: URL(fileURLWithPath: imagePath)),
- let image = UIImage(data: imageData) else {
- return nil
- }
- return image
- }
- // WebP格式 - 更高效的图片格式,需要第三方库支持
- func loadWebPImage(named name: String) -> UIImage? {
- // 这里需要使用第三方库如SDWebImage或Kingfisher来加载WebP图片
- // 示例代码假设使用SDWebImage
- if let imagePath = Bundle.main.path(forResource: name, ofType: "webp") {
- return SDWebImageManager.shared.imageCache?.imageFromDiskCache(forKey: imagePath)
- }
- return nil
- }
复制代码
使用图像降采样
- extension UIImage {
- func downsample(to size: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
- guard let imageData = self.jpegData(compressionQuality: 1.0) ?? self.pngData() else { return nil }
-
- let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
- guard let imageSource = CGImageSourceCreateWithData(imageData as CFData, imageSourceOptions) else { return nil }
-
- let maxDimensionInPixels = max(size.width, size.height) * scale
-
- let downsampleOptions = [
- kCGImageSourceCreateThumbnailFromImageAlways: true,
- kCGImageSourceShouldCacheImmediately: true,
- kCGImageSourceCreateThumbnailWithTransform: true,
- kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
- ] as CFDictionary
-
- guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
-
- return UIImage(cgImage: downsampledImage, scale: scale, orientation: self.imageOrientation)
- }
- }
- // 使用示例
- if let originalImage = UIImage(named: "large_image") {
- if let downsampledImage = originalImage.downsample(to: CGSize(width: 200, height: 200)) {
- // 使用降采样后的图片
- print("Original image memory usage: \(originalImage.estimatedMemoryUsage / (1024 * 1024)) MB")
- print("Downsampled image memory usage: \(downsampledImage.estimatedMemoryUsage / (1024 * 1024)) MB")
- }
- }
复制代码
使用图像池和缓存策略
- class ImageManager {
- static let shared = ImageManager()
- private var imageCache = NSCache<NSString, UIImage>()
-
- private init() {
- // 设置缓存限制
- let memoryCapacity = 100 * 1024 * 1024 // 100MB
- let preferredMemoryCapacity = 50 * 1024 * 1024 // 50MB
- imageCache.totalCostLimit = memoryCapacity
- imageCache.countLimit = 50 // 最多缓存50张图片
-
- // 监听内存警告
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- }
-
- func loadImage(named name: String, size: CGSize? = nil) -> UIImage? {
- let cacheKey = "\(name)-\(size?.width ?? 0)-\(size?.height ?? 0)" as NSString
-
- // 检查缓存
- if let cachedImage = imageCache.object(forKey: cacheKey) {
- return cachedImage
- }
-
- // 加载图片
- guard var image = UIImage(named: name) else { return nil }
-
- // 调整尺寸
- if let size = size {
- image = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
-
- return image
- }
-
- func preloadImages(names: [String], size: CGSize? = nil) {
- DispatchQueue.global(qos: .background).async {
- for name in names {
- _ = self.loadImage(named: name, size: size)
- }
- }
- }
- }
- // 使用示例
- let imageManager = ImageManager.shared
- // 加载图片
- if let image = imageManager.loadImage(named: "example_image", size: CGSize(width: 200, height: 200)) {
- // 使用图片
- }
- // 预加载图片
- imageManager.preloadImages(names: ["image1", "image2", "image3"], size: CGSize(width: 100, height: 100))
复制代码
解决卡顿问题
图片处理中的卡顿问题通常是由于主线程阻塞或内存压力导致的。以下是几种解决卡顿问题的方法。
异步图片加载和处理
- class AsyncImageLoader {
- static let shared = AsyncImageLoader()
- private var imageCache = NSCache<NSString, UIImage>()
- private var taskCache = [NSString: URLSessionDataTask]()
-
- private init() {
- imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- taskCache.values.forEach { $0.cancel() }
- taskCache.removeAll()
- }
-
- func loadImage(from url: URL, size: CGSize? = nil, completion: @escaping (UIImage?) -> Void) {
- let cacheKey = url.absoluteString as NSString
-
- // 检查缓存
- if let cachedImage = imageCache.object(forKey: cacheKey) {
- completion(cachedImage)
- return
- }
-
- // 检查是否有正在进行的任务
- if let existingTask = taskCache[cacheKey] {
- // 如果已有任务在加载此图片,则不再创建新任务
- return
- }
-
- // 创建并启动数据任务
- let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
- guard let self = self else { return }
-
- // 移除任务缓存
- self.taskCache.removeValue(forKey: cacheKey)
-
- guard let data = data, error == nil, let image = UIImage(data: data) else {
- DispatchQueue.main.async {
- completion(nil)
- }
- return
- }
-
- // 调整尺寸
- var finalImage = image
- if let size = size {
- finalImage = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- self.imageCache.setObject(finalImage, forKey: cacheKey, cost: finalImage.estimatedMemoryUsage)
-
- DispatchQueue.main.async {
- completion(finalImage)
- }
- }
-
- // 缓存任务
- taskCache[cacheKey] = task
- task.resume()
- }
-
- func cancelLoad(for url: URL) {
- let cacheKey = url.absoluteString as NSString
- taskCache[cacheKey]?.cancel()
- taskCache.removeValue(forKey: cacheKey)
- }
- }
- // 使用示例
- let loader = AsyncImageLoader.shared
- if let url = URL(string: "https://example.com/image.jpg") {
- loader.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
- if let image = image {
- // 在主线程更新UI
- DispatchQueue.main.async {
- self.imageView.image = image
- }
- }
- }
- }
复制代码
使用预加载和懒加载策略
- class ImagePreloader {
- static let shared = ImagePreloader()
- private var preloadQueue = DispatchQueue(label: "com.example.imagePreloader", qos: .background)
- private var imageCache = NSCache<NSString, UIImage>()
-
- private init() {
- imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- }
-
- func preloadImages(from urls: [URL], size: CGSize? = nil) {
- preloadQueue.async {
- for url in urls {
- let cacheKey = url.absoluteString as NSString
-
- // 如果图片已经在缓存中,则跳过
- if self.imageCache.object(forKey: cacheKey) != nil {
- continue
- }
-
- // 下载图片
- if let data = try? Data(contentsOf: url),
- var image = UIImage(data: data) {
-
- // 调整尺寸
- if let size = size {
- image = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
- }
- }
- }
- }
-
- func getImage(for url: URL) -> UIImage? {
- let cacheKey = url.absoluteString as NSString
- return imageCache.object(forKey: cacheKey)
- }
- }
- // 使用示例
- let preloader = ImagePreloader.shared
- // 预加载图片
- let imageUrls = [
- URL(string: "https://example.com/image1.jpg")!,
- URL(string: "https://example.com/image2.jpg")!,
- URL(string: "https://example.com/image3.jpg")!
- ]
- preloader.preloadImages(from: imageUrls, size: CGSize(width: 200, height: 200))
- // 在需要时获取图片
- if let url = URL(string: "https://example.com/image1.jpg"),
- let preloadedImage = preloader.getImage(for: url) {
- imageView.image = preloadedImage
- }
复制代码
使用占位图和渐进式加载
- class ProgressiveImageLoader {
- static let shared = ProgressiveImageLoader()
- private var imageCache = NSCache<NSString, UIImage>()
-
- private init() {
- imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- }
-
- func loadImageWithPlaceholder(
- from url: URL,
- placeholder: UIImage? = nil,
- size: CGSize? = nil,
- completion: @escaping (UIImage?) -> Void
- ) {
- let cacheKey = url.absoluteString as NSString
-
- // 检查缓存
- if let cachedImage = imageCache.object(forKey: cacheKey) {
- completion(cachedImage)
- return
- }
-
- // 先显示占位图
- DispatchQueue.main.async {
- completion(placeholder)
- }
-
- // 异步加载图片
- DispatchQueue.global(qos: .background).async { [weak self] in
- guard let self = self else { return }
-
- if let data = try? Data(contentsOf: url),
- var image = UIImage(data: data) {
-
- // 调整尺寸
- if let size = size {
- image = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
-
- DispatchQueue.main.async {
- completion(image)
- }
- } else {
- DispatchQueue.main.async {
- completion(placeholder)
- }
- }
- }
- }
- }
- // 使用示例
- let loader = ProgressiveImageLoader.shared
- let placeholder = UIImage(named: "placeholder")
- if let url = URL(string: "https://example.com/image.jpg") {
- loader.loadImageWithPlaceholder(
- from: url,
- placeholder: placeholder,
- size: CGSize(width: 200, height: 200)
- ) { image in
- self.imageView.image = image
- }
- }
复制代码
使用图像解码优化
- extension UIImage {
- func decodedImage() -> UIImage? {
- guard let cgImage = cgImage else { return nil }
-
- let size = CGSize(width: cgImage.width, height: cgImage.height)
- let colorSpace = CGColorSpaceCreateDeviceRGB()
- let context = CGContext(
- data: nil,
- width: Int(size.width),
- height: Int(size.height),
- bitsPerComponent: 8,
- bytesPerRow: 0,
- space: colorSpace,
- bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
- )
-
- context?.draw(cgImage, in: CGRect(origin: .zero, size: size))
-
- guard let decodedImage = context?.makeImage() else { return nil }
-
- return UIImage(cgImage: decodedImage, scale: scale, orientation: imageOrientation)
- }
- }
- class OptimizedImageLoader {
- static let shared = OptimizedImageLoader()
- private var imageCache = NSCache<NSString, UIImage>()
- private let processingQueue = DispatchQueue(label: "com.example.imageProcessing", qos: .userInitiated)
-
- private init() {
- imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- }
-
- func loadAndDecodeImage(
- from url: URL,
- size: CGSize? = nil,
- completion: @escaping (UIImage?) -> Void
- ) {
- let cacheKey = url.absoluteString as NSString
-
- // 检查缓存
- if let cachedImage = imageCache.object(forKey: cacheKey) {
- completion(cachedImage)
- return
- }
-
- // 异步加载和解码图片
- processingQueue.async { [weak self] in
- guard let self = self else { return }
-
- if let data = try? Data(contentsOf: url),
- var image = UIImage(data: data)?.decodedImage() {
-
- // 调整尺寸
- if let size = size {
- image = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
-
- DispatchQueue.main.async {
- completion(image)
- }
- } else {
- DispatchQueue.main.async {
- completion(nil)
- }
- }
- }
- }
- }
- // 使用示例
- let loader = OptimizedImageLoader.shared
- if let url = URL(string: "https://example.com/image.jpg") {
- loader.loadAndDecodeImage(from: url, size: CGSize(width: 200, height: 200)) { image in
- self.imageView.image = image
- }
- }
复制代码
打造流畅用户体验
除了技术层面的优化,用户体验也是图片处理的重要考量因素。以下是几种提升用户体验的方法。
实现平滑的图片过渡效果
- extension UIImageView {
- func setImageWithTransition(
- _ newImage: UIImage?,
- duration: TimeInterval = 0.3,
- options: UIView.AnimationOptions = .transitionCrossDissolve
- ) {
- UIView.transition(
- with: self,
- duration: duration,
- options: options,
- animations: {
- self.image = newImage
- },
- completion: nil
- )
- }
- }
- // 使用示例
- if let url = URL(string: "https://example.com/image.jpg") {
- AsyncImageLoader.shared.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
- self.imageView.setImageWithTransition(image)
- }
- }
复制代码
实现图片淡入效果
- extension UIImageView {
- func setImageWithFade(_ newImage: UIImage?, duration: TimeInterval = 0.5) {
- image = newImage
- alpha = 0
-
- UIView.animate(withDuration: duration) {
- self.alpha = 1
- }
- }
- }
- // 使用示例
- if let url = URL(string: "https://example.com/image.jpg") {
- AsyncImageLoader.shared.loadImage(from: url, size: CGSize(width: 200, height: 200)) { image in
- self.imageView.setImageWithFade(image)
- }
- }
复制代码
实现渐进式图片加载
- class ProgressiveImageView: UIImageView {
- private var currentTask: URLSessionDataTask?
-
- func loadProgressiveImage(from url: URL, placeholder: UIImage? = nil) {
- // 取消之前的任务
- currentTask?.cancel()
-
- // 设置占位图
- image = placeholder
-
- // 创建请求
- let request = URLRequest(url: url)
-
- // 创建数据任务
- currentTask = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
- guard let self = self, let data = data, error == nil else {
- return
- }
-
- // 在后台队列处理图片
- DispatchQueue.global(qos: .userInitiated).async {
- if let image = UIImage(data: data) {
- DispatchQueue.main.async {
- self.setImageWithTransition(image)
- }
- }
- }
- }
-
- // 启动任务
- currentTask?.resume()
- }
-
- func cancelLoad() {
- currentTask?.cancel()
- currentTask = nil
- }
- }
- // 使用示例
- let progressiveImageView = ProgressiveImageView()
- if let url = URL(string: "https://example.com/image.jpg") {
- progressiveImageView.loadProgressiveImage(from: url, placeholder: UIImage(named: "placeholder"))
- }
复制代码
实现图片预加载和缓存策略
- class SmartImageCache {
- static let shared = SmartImageCache()
- private var memoryCache = NSCache<NSString, UIImage>()
- private var diskCache: URLCache
- private let fileManager = FileManager.default
- private let cachePath: URL
-
- private init() {
- // 内存缓存设置
- memoryCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- memoryCache.countLimit = 50 // 最多缓存50张图片
-
- // 磁盘缓存设置
- let diskCapacity = 500 * 1024 * 1024 // 500MB
- diskCache = URLCache(memoryCapacity: 0, diskCapacity: diskCapacity, diskPath: nil)
-
- // 创建缓存目录
- if let cachesURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first {
- cachePath = cachesURL.appendingPathComponent("ImageCache")
-
- try? fileManager.createDirectory(at: cachePath, withIntermediateDirectories: true, attributes: nil)
- } else {
- cachePath = URL(fileURLWithPath: NSTemporaryDirectory())
- }
-
- // 监听内存警告
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearMemoryCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearMemoryCache() {
- memoryCache.removeAllObjects()
- }
-
- // 获取图片
- func image(for url: URL, completion: @escaping (UIImage?) -> Void) {
- let cacheKey = url.absoluteString as NSString
-
- // 检查内存缓存
- if let cachedImage = memoryCache.object(forKey: cacheKey) {
- completion(cachedImage)
- return
- }
-
- // 检查磁盘缓存
- if let cachedResponse = diskCache.cachedResponse(for: URLRequest(url: url)),
- let image = UIImage(data: cachedResponse.data) {
- // 存入内存缓存
- memoryCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
- completion(image)
- return
- }
-
- // 从网络加载
- downloadImage(from: url) { [weak self] image in
- guard let self = self, let image = image else {
- completion(nil)
- return
- }
-
- // 存入内存缓存
- self.memoryCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
-
- // 存入磁盘缓存
- if let data = image.jpegData(compressionQuality: 0.8) {
- let response = URLResponse(url: url, mimeType: "image/jpeg", expectedContentLength: data.count, textEncodingName: nil)
- let cachedResponse = CachedURLResponse(response: response, data: data)
- self.diskCache.storeCachedResponse(cachedResponse, for: URLRequest(url: url))
- }
-
- completion(image)
- }
- }
-
- // 下载图片
- private func downloadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
- URLSession.shared.dataTask(with: url) { data, response, error in
- guard let data = data, error == nil else {
- DispatchQueue.main.async {
- completion(nil)
- }
- return
- }
-
- let image = UIImage(data: data)
- DispatchQueue.main.async {
- completion(image)
- }
- }.resume()
- }
-
- // 预加载图片
- func preloadImages(from urls: [URL]) {
- DispatchQueue.global(qos: .background).async {
- for url in urls {
- _ = self.image(for: url) { _ in }
- }
- }
- }
-
- // 清除所有缓存
- func clearAllCache() {
- clearMemoryCache()
- diskCache.removeAllCachedResponses()
-
- try? fileManager.removeItem(at: cachePath)
- try? fileManager.createDirectory(at: cachePath, withIntermediateDirectories: true, attributes: nil)
- }
- }
- // 使用示例
- let smartCache = SmartImageCache.shared
- // 加载图片
- if let url = URL(string: "https://example.com/image.jpg") {
- smartCache.image(for: url) { image in
- if let image = image {
- self.imageView.image = image
- }
- }
- }
- // 预加载图片
- let imageUrls = [
- URL(string: "https://example.com/image1.jpg")!,
- URL(string: "https://example.com/image2.jpg")!,
- URL(string: "https://example.com/image3.jpg")!
- ]
- smartCache.preloadImages(from: imageUrls)
复制代码
实现图片懒加载和优先级加载
- enum ImageLoadPriority {
- case high
- case normal
- case low
- }
- class PriorityImageLoader {
- static let shared = PriorityImageLoader()
- private var highPriorityQueue = OperationQueue()
- private var normalPriorityQueue = OperationQueue()
- private var lowPriorityQueue = OperationQueue()
- private var imageCache = NSCache<NSString, UIImage>()
-
- private init() {
- // 配置队列
- highPriorityQueue.qualityOfService = .userInitiated
- normalPriorityQueue.qualityOfService = .utility
- lowPriorityQueue.qualityOfService = .background
-
- // 设置最大并发操作数
- highPriorityQueue.maxConcurrentOperationCount = 2
- normalPriorityQueue.maxConcurrentOperationCount = 3
- lowPriorityQueue.maxConcurrentOperationCount = 5
-
- // 配置缓存
- imageCache.totalCostLimit = 100 * 1024 * 1024 // 100MB
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(clearCache),
- name: UIApplication.didReceiveMemoryWarningNotification,
- object: nil
- )
- }
-
- @objc private func clearCache() {
- imageCache.removeAllObjects()
- }
-
- func loadImage(
- from url: URL,
- size: CGSize? = nil,
- priority: ImageLoadPriority = .normal,
- completion: @escaping (UIImage?) -> Void
- ) {
- let cacheKey = url.absoluteString as NSString
-
- // 检查缓存
- if let cachedImage = imageCache.object(forKey: cacheKey) {
- completion(cachedImage)
- return
- }
-
- // 创建操作
- let operation = BlockOperation { [weak self] in
- guard let self = self else { return }
-
- if let data = try? Data(contentsOf: url),
- var image = UIImage(data: data) {
-
- // 调整尺寸
- if let size = size {
- image = image.resized(to: size) ?? image
- }
-
- // 缓存图片
- self.imageCache.setObject(image, forKey: cacheKey, cost: image.estimatedMemoryUsage)
-
- DispatchQueue.main.async {
- completion(image)
- }
- } else {
- DispatchQueue.main.async {
- completion(nil)
- }
- }
- }
-
- // 根据优先级添加到相应队列
- switch priority {
- case .high:
- highPriorityQueue.addOperation(operation)
- case .normal:
- normalPriorityQueue.addOperation(operation)
- case .low:
- lowPriorityQueue.addOperation(operation)
- }
- }
-
- func cancelAllOperations() {
- highPriorityQueue.cancelAllOperations()
- normalPriorityQueue.cancelAllOperations()
- lowPriorityQueue.cancelAllOperations()
- }
- }
- // 使用示例
- let loader = PriorityImageLoader.shared
- // 加载高优先级图片
- if let url = URL(string: "https://example.com/important_image.jpg") {
- loader.loadImage(from: url, priority: .high) { image in
- if let image = image {
- self.imageView.image = image
- }
- }
- }
- // 加载普通优先级图片
- if let url = URL(string: "https://example.com/normal_image.jpg") {
- loader.loadImage(from: url, priority: .normal) { image in
- if let image = image {
- self.secondaryImageView.image = image
- }
- }
- }
- // 加载低优先级图片
- if let url = URL(string: "https://example.com/background_image.jpg") {
- loader.loadImage(from: url, priority: .low) { image in
- if let image = image {
- self.backgroundImageView.image = image
- }
- }
- }
复制代码
总结
在Swift中有效管理UIImage的尺寸对于优化内存使用和提升应用性能至关重要。本文介绍了多种获取、调整和优化图片尺寸的方法,以及如何解决卡顿问题和打造流畅的用户体验。
关键要点包括:
1. 获取图片尺寸:了解如何从不同来源获取图片尺寸信息,包括资源文件、文件URL和网络。
2. 调整图片尺寸:掌握多种图片尺寸调整方法,包括简单调整、高质量调整、使用Core Image和使用vImage进行高性能调整。
3. 内存优化:通过计算图片内存占用、使用适当格式的图片、图像降采样和缓存策略来优化内存使用。
4. 解决卡顿问题:使用异步图片加载和处理、预加载和懒加载策略、占位图和渐进式加载以及图像解码优化来解决卡顿问题。
5. 打造流畅用户体验:实现平滑的图片过渡效果、图片淡入效果、渐进式图片加载、图片预加载和缓存策略以及图片懒加载和优先级加载。
获取图片尺寸:了解如何从不同来源获取图片尺寸信息,包括资源文件、文件URL和网络。
调整图片尺寸:掌握多种图片尺寸调整方法,包括简单调整、高质量调整、使用Core Image和使用vImage进行高性能调整。
内存优化:通过计算图片内存占用、使用适当格式的图片、图像降采样和缓存策略来优化内存使用。
解决卡顿问题:使用异步图片加载和处理、预加载和懒加载策略、占位图和渐进式加载以及图像解码优化来解决卡顿问题。
打造流畅用户体验:实现平滑的图片过渡效果、图片淡入效果、渐进式图片加载、图片预加载和缓存策略以及图片懒加载和优先级加载。
通过合理应用这些技术,你可以显著提升iOS应用的图片处理性能,减少内存占用,避免卡顿问题,为用户提供流畅的体验。记住,最佳实践往往是根据具体应用场景和需求来选择合适的策略,而不是盲目追求最复杂或最高性能的解决方案。 |
|