문제 인식
What is Task Continuation Misuse??
- Continuation을 적절히 호출하지 않거나 여러 번 호출하는 경우에 Swift 런타임이 감지하여 발생하는 오류
- 기존 completionHandler나 delegate에서 처리를 받은 작업의 흐름에 문제가 생겼다는 것임
⇒ 문제 발생의 경우resume
을 반드시 한 번만 호출했는가?- 모든 코드 흐름에서
resume
이 호출되도록 보장했는가? resume
을 호출하기 전에 비동기 작업이 완료되었는가?
Task Continuation Misuse에 대한 자세한 설명은 다음 링크에...
https://velog.io/@dvhuni/Continuation
Continuation
안녕하세요~!!! 🥰 이번 포스트는 Concurrency !! 그중에서도 Continuation에 대해 알아보겠습니다!! 🤓
velog.io
기존 코드
Thumbnail 추출하는 메서드: .convertToUIImage
라는 메서드를 통해서 크기를 조절한 이미지를 반환한다.
actor ThumbnailExecutor{
private var result: PHFetchResult<PHAsset>!
...
func run() async{
... // PHFetchResult<PHAsset> 타입을 통해 사용자가 고른 이미지들을 찾는다.
result.enumerateObjects(options:.concurrent) { asset, _, _ in
Task{
// 각각의 Asset을 UIImage로 변환한다.
let image = try await asset.convertToUIImage(size: .init(width: 120, height: 120 * 1.3333))
...
}
}
}
결국 핵심 문제는 .convertToUIImage
메서드인데… 사실 이건 내가 직접 만든 메서드이다
convertToUIImage(size:CGSize? = nil) async throws -> UIImage
extension PHAsset{
func convertToUIImage(size:CGSize? = nil) async throws -> UIImage{
return try await withCheckedThrowingContinuation { [weak self] continuation in
self?.**requestContentEditingInput**(with: nil) { input, info in
guard let input, let imageURL = input.fullSizeImageURL else {return}
let imageSourceOption = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource: CGImageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOption)!
let image = self!.coreDownSample(resource: imageSource,size: size)
continuation.resume(returning: image)
}
}
}
}
✅ 코드 설명
withCheckedThrowingContinuation()
⇒ 전통적인 completionHandler를 이용한 비동기처리를 async-await 구문으로 바꾸어주는 swift 기본 메서드requestContentEditingInput()
⇒PHAsset
를 통해PHContentEditingInput
과 추가 정보 데이터로 반환하는 메서드PHContentEditingInput
값을CGImageResource
로 바꾼다.CGImageResource
로 담긴 이미지를 스크린 사이즈에 맞게 DownSampling 후UIImage
로 반환한다.
➕ 추가적인 코드 설명
coreDownSample
은 CGImageSource로 부터 CGImage → UIImage로 변환하는 메서드다.
해결 방법
💡 기존 코드에 PHContentEditingInputRequestOptions()의 isNetworkAccessAllowed = true 추가
✅ isNetworkAccessAllowed??
➡️ iOS에서 iCloud로 저장한 이미지를 가져오는 것을 허용한다.
func convertToUIImage(size:CGSize? = nil) async throws -> UIImage{
return try await withCheckedThrowingContinuation { [weak self] continuation in
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true
self?.requestContentEditingInput(with: options) { input, info in
guard let input, let imageURL = input.fullSizeImageURL else {return}
let imageSourceOption = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource: CGImageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOption)!
let image = self!.coreDownSample(resource: imageSource,size: size)
continuation.resume(returning: image)
}
}
}
너무 느린 문제가 발생했다,,, 느리다면 Task Continuation Misuse 문제의 원인이 될 수 있다는 문제도 있다..!
🥲 저 이미지는 2배속 한 이미지...
⇒ resume을 가져오지 못해..!
개선 방법 #1 convertToUIImage
처리를 담당하는 고유 객체 생성하기
final class PHAssetToUIImageContainer {
private var checkedContinuation : CheckedContinuation<UIImage, any Error>?
private func _convertToUIImage(phAsset: PHAsset,size: CGSize? = nil){
phAsset.requestContentEditingInput(with: nil) { input, info in
guard let input, let imageURL = input.fullSizeImageURL else {return}
let imageSourceOption = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource: CGImageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOption)!
let image = phAsset.coreDownSample(resource: imageSource,size: size)
self.checkedContinuation?.resume(returning: image)
self.checkedContinuation = nil
}
}
func convertToUIImage(size:CGSize? = nil) async throws -> UIImage {
return try await withCheckedThrowingContinuation { continuation in
self.checkedContinuation = continuation
}
}
}
- 일반적인 Task Continuation Misuse와 관련된 구글링을 한다면 얻는 간단한 해결법
continuation
을 특정 객체가 옵셔널 값으로 가져 잘못된 경우에 대한 처리를nil
로 반환함, 비동기에 대한 근본적인 문제 해결이라 보긴 어려움- PHAsset의 Extension 메서드로 동작하지 못하고 굳이 새로운 객체를 생성하는 방법을 사용해야하는지 의문
- 근본적으로
requestContentEditingInput
에 대한 처리가 잘못되어서 저런 오류를 뱉는것이 아닐까? Swift 컴파일러가 바보가 아닌이상!!
⇒ 다른 방법을 찾아보기로 함
✅ 개선 방법 #2 PHCachingImageManager 사용하기
PHCachingImageManager | Apple Developer Documentation
An object that facilitates retrieving or generating preview thumbnails, optimized for batch preloading large numbers of assets.
developer.apple.com
"미리보기 썸네일을 쉽게 검색하거나 생성할 수 있는 오브젝트로, 많은 수의 에셋을 일괄적으로 미리 로드하는 데 최적화되어 있습니다."
➕ 개별 에셋에 대한 이미지가 필요한 경우 요청
⇒ "이미 (for:targetSize:contentMode:options:resultHandler:)
메서드를 호출하고 해당 에셋을 준비할 때 사용한 것과 동일한 파라미터를 전달합니다."
✅ 공식 문서 overview 4번
Thumbnail을 추출하는 메서드
actor ThumbnailExecutor{
private var result: PHFetchResult<PHAsset>!
private let imageManager: PHCachingImageManager = .init()
...
func run() async{
... // PHFetchResult<PHAsset> 타입을 통해 사용자가 고른 이미지들을 찾는다.
result.enumerateObjects(options:.concurrent) { asset, _, _ in
self.fetchImage(phAsset: asset, size: .init(width: 3 * 120, height: 3 * 120 * 1.77), contentMode: .aspectFill) {
...
}
}
}
private func fetchImage(phAsset: PHAsset,
size: CGSize,
contentMode: PHImageContentMode,
completion: @escaping (UIImage) -> Void) {
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true // iCloud
options.deliveryMode = .highQualityFormat
let reqestHandler :(UIImage?, [AnyHashable : Any]?) -> Void = { image, _ in
guard let image else { return }
completion(image)
}
imageManager.requestImage(for: phAsset,
targetSize: size,
contentMode: contentMode,
options: options,
resultHandler:reqestHandler
)
}
- fetchImage는
PHCachingImageManager
의requestImage
메서드를 호출하는데 필요한 여러 구성 로직을 감싼다.
개선방법 #2를 적용한 최종 결과
PHCachingImageManager
를 사용한 결과, 최소95%
의 응답속도의 성능 향상을 보여줘 빠르게 이미지들을 가져오는 것을 확인할 수 있다.resultHandler
에서 UIImage 반환 타입이nil
이므로 반환하지 못할 가능성이 있다.- 뭐 iCloud에 이미지를 가져올 때, 비행기 모드를 켠다거나 그런 오류일 수도??
- 나중에 사용자에게 이런 이미지를 가져오지 못한 에러 처리에 대한 안내를 할 필요가 있겠다.
'이모저모 > AVFoundation' 카테고리의 다른 글
AVFoundation - 영상을 이미지 배열로 저장 시 메모리 초과되는 이슈, Accelerate (0) | 2025.03.27 |
---|---|
12주차. 멀티미디어 시스템 & 스트리밍 (0) | 2024.07.28 |
11주차. 오디오 압축 및 처리 (0) | 2024.07.23 |
9주차. 동영상 압축기술(1) (0) | 2024.07.07 |
7주차. 영상처리(Image processing) (0) | 2024.07.07 |
댓글