관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 8. Memory Deep Dive - 이미지 리사이징, 이미지 다운 샘플링 (ImageIO, ImageSource) 본문

iOS 응용 (swift)

[iOS - swift] 8. Memory Deep Dive - 이미지 리사이징, 이미지 다운 샘플링 (ImageIO, ImageSource)

jake-kim 2023. 12. 15. 01:06

* 가장 기초) iOS 메모리 기초 개념 - virtual memory, dirty memory, clean memory, compressed memory, swapped memory 이해하기 포스팅 글

 

1. Memory Deep Dive - iOS 메모리 운영체제 기초 (가상 메모리, 페이징, clean memory, dirty memory, compressed memory)

2. Memory Deep Dive - Memory를 줄여야 하는 이유 (+ 앱 메모리 사용량 아는 방법)

3. Memory Deep Dive - Memory Footprint (페이징, Compressed 메모리)

4. Memory Deep Dive - Memory Footprint 프로파일링 방법 (Allocation, Leaks, VM Tracker, Virtual memory trace)

5. Memory Deep Dive - Memory Footprint 프로파일링 방법 (2) (Xcode Memory Debugger, Memory Graph, Memgraph)

6. Memory Deep Dive - Memory Footprint 프로파일링 방법 (3) (vmmap, leaks, heap, malloc_history)

7. Memory Deep Dive - 이미지 로드 매커니즘, 이미지 핸들링 최적화 (UIGraphicsBeginImageContextWithOptions, UIGraphicsImageRenderer)

8. Memory Deep Dive - 이미지 리사이징, 이미지 다운 샘플링 (ImageIO, ImageSource)

9. Memory Deep Dive - 백그라운드에서 메모리 최적화하는 방법

ImageIO - 다운 샘플링

  • UIImage는 resizing하기에는 많은 비용이 필요
    • 메모리에 original image를 그대로 저장하기에는 위험이 있어서 메모리에서 decode하기전에 다운 샘플링이 필요 (decode관련 개념은 이전 포스팅 이미지 로드 매커니즘 글 참고)
  • ImageIO는 image 사이즈와 메타 데이터를 dirty memory로 사용하지 않고 읽기가 가능
  • 메모리에 original image를 load하기전에 imageIO를 통해서 사이즈를 파악하는것이 핵심
  • UIGraphics와 같은 UIImage API를 사용하면 메모리에 이미지 전체를 적재하므로 상당히 비효율적
  • ImageIO를 사용하여 다운 샘플링된 결과 이미지만 dirty memory에 올리는것이 목적

ImageIO를 안쓰는 일반적인 방식

  • 보통 ImageIO를 사용하지 않으면, 이전 포스팅 글에서 알아보았듯이 UIGraphicsBeginImageContextWithOptions가 아닌 UIGraphicsImageRenderer를 사용하여 이미지 렌더링을 수행
import UIKit

class ViewController: UIViewController {
    private let imageView = UIImageView()
    
    override func viewDidLoad() {
        ...
    }
    
    func downloadWithResizeV1() {
        guard let url = URL(string: "https://cdn.pixabay.com/photo/2023/11/30/07/51/bridge-8420945_1280.jpg") else { return }
        downloadImageData(from: url) { [weak self] data in
            guard let data, let image = UIImage(data: data) else { return }
            DispatchQueue.main.async {
                self?.imageView.image = image
            }
            
            // resizing image
            let scale = 0.2
            let size = CGSize(width: image.size.width * scale, height: image.size.height * scale)
            let renderer = UIGraphicsImageRenderer(size: size)
            let renderedImage = renderer.image { _ in
                image.draw(in: .init(origin: .zero, size: size))
            }
            DispatchQueue.main.async {
                self?.imageView.image = renderedImage
            }
        }
    }
}

private extension ViewController {
    func downloadImageData(from url: URL, completion: @escaping (Data?) -> Void) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                completion(nil)
                return
            }
            completion(data)
        }.resume()
    }
}
  • 위 코드의 문제점: url로부터 가져온 이미지 원본 자체를 dirty memory에 올려서 메모리 효율이 매우 안좋아짐 (dirty memory 개념은 이전 포스팅 글 참고)

ImageIO를 통한 이미지 리사이징 최적화

  • CGImageSourceCreateWithURL로 인스턴스 획득
let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)!
  • 이미지의 크기 설정 및 몇개의 파라미터를 통해 리사이징된 이미지 획득
let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
let options: [CFString : Any] = [
    kCGImageSourceThumbnailMaxPixelSize: 100,
    kCGImageSourceCreateThumbnailFromImageAlways: true
]
let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)
  • 얻어진 CGImage를 UIImage로 감싸서 사용
    • dirty memory에 이 scaledImage만 올리게 되어 메모리 효율이 높고 이전 코드보다 50%더 빠른 이점

완성 코드)

func downloadWithResizeV2() {
    let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)!
    let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
    let options: [CFString : Any] = [
        kCGImageSourceThumbnailMaxPixelSize: 100,
        kCGImageSourceCreateThumbnailFromImageAlways: true
    ]
    let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)!
    let image = UIImage(cgImage: scaledImage)
    imageView.image = image
}

 

cf) 이 이미지 다루는 것들을 백그라운드에서 최적화 할 수 있는데 이 내용은 다음 포스팅 글에서 계속...

 

* 전체 코드: https://github.com/JK0369/ExImageIO

* 참고

- https://developer.apple.com/videos/play/wwdc2018/416/

- https://pixabay.com/ko/

Comments