처음 Image Cache를 구현했을 때 사용한 방식은 다음과 같습니다.
프로필 사진의 경우
1. memory cache (NSCache)
2. disk cache (File Manager)
3. Network 요청 후 memory, disk cache 저장
피드 이미지의 경우
1. memory cache (NSCache)
2. Network 요청 후 memory, disk cache 저장
개선점
1. NSCache Limit
NSCache는 메모리를 많이 사용할 때 자동삭제 정책을 지원하지만, limit을 지정해주지 않으면 다 쓸 때까지 써버리는? 문제가 있었습니다.
위의 사진처럼 사용하면 할수록 올라가는 메모리..
var countLimit: Int
var totalCostLimit: Int
최대 몇개, 용량을 지정해주면 지정해준 값이 넘어갈 때 자동으로 삭제가 된다는 것을 알 수 있습니다.
테스트로 10개, 10MB로 설정해주고 메모리 사용량을 보았습니다.
final class ImageCacheManager: NSObject, NSCacheDelegate {
static let shared = ImageCacheManager(countLimit: 10, totalCostLimit: 1024 * 1024 * 10)
// static let shared = ImageCacheManager(countLimit: 100, totalCostLimit: 1024 * 1024 * 100)
private var cache = NSCache<NSString, UIImage>()
var cancelBag = Set<AnyCancellable>()
private init(
countLimit: Int,
totalCostLimit: Int
) {
super.init()
cache.delegate = self
cache.countLimit = countLimit
cache.totalCostLimit = totalCostLimit
}
}
이미지의 크기가 너무 제각각이라 (원본을 저장합니다.) 일정하지 않은 메모리 사용량을 볼 수 있지만 200MB가 넘었을 때보다 적어진 사용량을 볼 수 있었습니다.
어느정도를 캐시 메모리로 잡아야할지는 이미지 크기를 일정하게 줄인 후 고려해보겠습니다.
삭제되는 이미지의 정보를 보았을 때 이미지를 원본으로 저장하기 때문에
이미지의 크기가 제각각이고 크기가 크다는 사실을 발견할 수 있었습니다.
2. 이미지의 크기
처음에는 홈화면의 썸네일 이미지와 디테일뷰의 이미지를 함께 생각해서 이미지의 크기를 어떻게 줄여야할지 고민했습니다.
하지만 디테일뷰는 웹뷰로 이미지를 함께 렌더링하기 때문에 (정확히 어떻게 불러오는지는 잘 모르겠지만 메모리가 크게 늘어나지 않아서)
UIImageView로 캐시로직을 고려할때 썸네일 이미지만 고려해서 이미지의 크기를 썸네일 이미지 크기로 줄이기로 결정했습니다.
킹피셔는 이 썸네일 이미지를 캐시하고 원본 이미지도 함께 저장하지만
버스트캠프는 원본이미지를 사실상 쓸 일이 없기 때문에 썸네일 이미지를 원본처럼 사용하기로 했습니다.
그러나 메모리 사용량을 줄이려면 resizing이 아니라 downSampling을 해야한다는 사실 .. !
간략히 이유를 적으면,
UIImage는 Data Buffer에서 Image Buffer로 디코딩하는 역할을 하는데
이 디코딩을 하는 작업은 비용이 굉장히 크기 때문에 이 버퍼 원본 사이즈를 줄여서 디코딩을 하면 메모리 사용량을 줄일 수 있습니다.
downsampling 으로 구현해보았습니다.
private func createThumnail(data: Data, size: CGSize) -> UIImage {
guard let cgImageSource = CGImageSourceCreateWithData(data as CFData, nil)
else { return UIImage() }
let thumnailOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height),
kCGImageSourceCreateThumbnailWithTransform: true
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(cgImageSource, 0, thumnailOptions)
else { return UIImage() }
return UIImage(cgImage: cgImage)
}
kCGImageSourceThumbnailMaxPixelSize 를 썸네일의 가로크기, 100으로 맞췄을 때
썸네일의 크기가 100*75 라서 100으로 맞춰줬더니 이미지가 매우 흐릿하게 나오는 현상이 발생했습니다.
이미지 에셋을 줄 때 100, x2, x3으로 레티나 디스플레이를 생각해서 300으로 맞춰줬습니다.
kCGImageSourceThumbnailMaxPixelSize 를 썸네일의 가로크기, 300으로 맞췄을 때
가끔 흐릿한 이미지가 있는데 어느정도 맞춰진 모습을 볼 수 있었습니다.
이미지를 다운샘플링하니 대략 60-70 MB의 메모리를 줄일 수 있었습니다.
(아직 NSCache 메모리는 10MB...ㅎㅎㅎ인데 왜 저렇게까지 올라가는 것인지..)
kCGImageSourceThumbnailMaxPixelSize 요놈..
https://developer.apple.com/documentation/imageio/kcgimagesourcethumbnailmaxpixelsize
Apple Developer Documentation
developer.apple.com
private func createThumnail(data: Data) -> UIImage {
guard let cgImageSource = CGImageSourceCreateWithData(data as CFData, nil)
else { return UIImage() }
let thumnailOptions = [
kCGImageSourceShouldCache: true,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: 100 * 3, // 썸네일 이미지 100*75 (x3)
kCGImageSourceCreateThumbnailWithTransform: true
] as CFDictionary
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(cgImageSource, 0, thumnailOptions)
else { return UIImage() }
return UIImage(cgImage: cgImage)
}
애플 문서, wwdc 예제 코드와 다른 사람들의 예제 코드를 보았을 때
모두 kCGImageSourceThumbnailMaxPixelSize 최대 픽셀 값을
UIImage를 받아서 지정해주거나 cgSize로 지정하거나 직접 값을 넣어주는 방식으로 구현하고 있었습니다만..
UIImage로 받는 것은 UIImage로 디코딩 되기 전에 다운샘플링을 하는 것이 목적인데
UIImage를 받고 -> 다운 샘플링의 사이즈를 정해서 -> 다시 UIImage로 반환하는 것은
메모리를 줄이려는 목적과 일치하지 않는다고 생각해서 보류되었습니다..
그래서 직접 썸네일의 값 x3 = 300을 위에서 넣어주었는데 문제는 가로로 긴 이미지에 있었습니다.
300으로 줄여버리니 매우 깨져보이는 현상이 나타났습니다..ㅠㅠ
문제는 가져오는 이미지의 사이즈가 정말 모두~~~~ 제각각 (작은거, 큰거, 가로로 긴 것, 세로로 긴 것) 다르다는 것이었습니다...
이게 왜 문제가 되냐면
저는 Data 형식을 받아서 cgImage를 생성해주기 때문에 Data 형식에서 image Size를 알 수 없다는 것이었습니다..
그래서 미친듯이 구글링한 결과 아래 사이트에서 힌트를 얻을 수 있었습니다.
https://nshipster.com/image-resizing/
Image Resizing Techniques
Since time immemorial, iOS developers have been perplexed by a singular question: ‘How do you resize an image?’ This article endeavors to provide a clear answer to this eternal question.
nshipster.com
Accelerate.vImage 를 import 하면 저 vImagePixelCount라는 타입이 있는데
이 타입은 UInt를 typealias 한 것이라서
저 모듈을 import 하지 않고 UInt로 형변환해서 가져올 수 있었습니다.
이제 이 사이즈를 어떻게 줄이나..
3분의 1로 줄여보고, 이미지 압축 알고리즘을 찾아봤지만..
정확하게 비율, aspectFill을 고려한 maxPixel값을 도저히 찾을 수 없어서
임의로 300 아래는 제 값으로,
600 아래는 300 값으로 고정,
600 위는 2분의 1 값으로 고정하기로 했습니다.
private func maxPixel(width: UInt, height: UInt) -> Int {
let max = Int(max(width, height))
print("최대픽셀", maxPixel)
switch max {
case 0...300: return max
case 301...600: return ImageCacheManager.thumnailMaxPixel
default: return max / 2
}
}
결과는..!
완벽한 원본 구현은 아니지만, 메모리를 줄이고 어느정도 원본을 유지한 채
메모리 사용량을 (NSCache 용량 100MB) 일정 수준으로 유지할 수 있었습니다.
디스크 캐시를 개선하는 방법은 다음 포스트에..
이미지 캐시
https://medium.com/@mshcheglov/reusable-image-cache-in-swift-9b90eb338e8d
Reusable Image Cache in Swift
Almost every application contains some kind of graphics. That is the reason why downloading and displaying images in a mobile application…
medium.com
https://jeonyeohun.tistory.com/367
[iOS] 메이트러너: 반쪽짜리 이미지 캐시 개선하기
메이트 러너 앱 개발 과정을 공유하는 포스트입니다! GitHub - boostcampwm-2021/iOS06-MateRunner: 함께 달리는 즐거움, Mate Runner 🏃🏻♂️🏃🏻♀️ 함께 달리는 즐거움, Mate Runner 🏃🏻♂️🏃🏻
jeonyeohun.tistory.com
다운샘플링
https://motosw3600.tistory.com/m/12
Image Optimizing
Image Memory Size 2048x1536 픽셀의 590KB size의 이미지를 앱에서 로딩하면 얼마의 메모리가 필요할까? 약 14MB(2048x1536x4/1000000), 픽셀당 4바이트 가정 매우 많은 메모리 소비 Image Rendring Process 1. Load(iOS는 압
motosw3600.tistory.com
https://velog.io/@dev_jane/UICollectionView-이미지-처리-downsampling
UICollectionView 이미지 처리: downsampling(feat. WWDC Image and Graphics Best Practices)
문제 상황 검색 결과를 UICollectionView에 표시할때 크기가 큰 이미지가 들어오는 상황에서 이미지 로딩 속도가 느리고, 메모리 사용량이 급격하게 늘어남 스크롤시 이미지가 깜빡거리면서 바뀌기
velog.io
[WWDC 2018] iOS Memory Deep Dive (2/2)
WWDC 2018 iOS Memory Deep Dive (1/2) WWDC 2018 iOS Memory Deep Dive (2/2) Images 이미지에서 메모리 사용은 파일 크기가 아니라 이미지의 크기와 관련이 있다는 것입니다. why is it so much larger? SRGB Format 픽셀 당 8 비
hucet.tistory.com
이미지 리사이즈 방법 총정리 - Swift · Wireframe
최근에 이미지 크기를 일괄적으로 줄여서 리사이징하는 간단한 도구를 만들어 봤는데, 요상하게 결과 이미지가 흐려졌다. 결과가 맘에 들지 않아서 좀 더 고품질의 결과를 얻는 방법을 찾기 위
soooprmx.com
다운샘플링 - imageIO
https://bastian.codes/blog/improving-image-rendering-using-coregraphics/
Improving image rendering using ImageIO · bastian.codes
Jul 15, 2019 Improving image rendering using ImageIO Displaying images has become standard practice for many categories of apps. But if images rendered in applications are not pre-processed properly, it can have a negativ impact on performance and therefor
bastian.codes
다운샘플링 vs UIImage
https://developer.apple.com/forums/thread/109445
DownSampling / Scaling Image Quali… | Apple Developer Forums
It's been a while since you asked this question, but I'm gonna try to answer it anyway: I think this is due to your image view being 300 points in size, and your image being 300 pixels wide. Points =/= Pixels on iOS. For Example, on standard Retina display
developer.apple.com
다운샘플링imgeIO
'iOS > PROJECT' 카테고리의 다른 글
[burstcamp] Remote PushNotification 도입 2편 - 어디서나 푸시알림 받아서 디테일 화면 띄우기 (0) | 2022.12.10 |
---|---|
[burstcamp] 백엔드 개발자 없이.. Remote PushNotification 도입 1편 - Firebase Functions (0) | 2022.12.10 |
[burstcamp] 유저정보를 효율적으로 관리하는 방법 (with. 파이어베이스, KeyChain) (0) | 2022.12.03 |
[burstcamp] 다크모드 Switch 버튼 제공 (+iOS 15 대응) (0) | 2022.11.26 |
[burstcamp] XCTemplate (2) | 2022.11.26 |