iOS17에서의 UIGraphicsBeginImageContext 이슈
한 줄 요약
iOS 17 이상에서 UIGraphicsBeginImageContext()를 시도할 때, 이미지 사이즈가 (0, 0)인 경우 강제종료될 수 있으므로 UIGraphicsImageRenderer를 쓰는 것이 권장된다.
메이저 앱 배포 후 갑자기 ImageContext 관련해서 크래시리틱스에 이슈가 많이 나왔다.
**NSInternalInconsistencyException - UIGraphicsBeginImageContext() failed to allocate CGBitampContext: size={0, 0}, scale=1.000000, bitmapInfo=<address>. Use UIGraphicsImageRenderer to avoid this assert.**
이런 에러 메시지가 포함되어 있었는데, 콜스택이 특이했다. 내 경우엔 버튼의 isHighlighted가 set되는 걸 시작으로 타고타고 문제의 근본으로 내려가다보니 버튼의 이미지뷰 alpha 값을 조정하는 코드까지 내려갔다. 하지만 버튼의 isHighlighted가 정확히 어디서 set되는지는 콜스택에 없었다.
이럴 때는 Sentry에서 같은 에러 페이지를 열어놓고 좀 더 자세히 살펴보는 것이 도움이 된다. 센트리 에러의 Breadcrumbs를 보면 에러 전까지 어떤 타입의 이벤트들이 일어났는지 살펴볼 수 있다.
여러 케이스의 Breadcrumbs를 확인해보니 공통적으로 콘텐츠 다운로드를 하고 있었다는 것을 추리할 수 있었다.
다운로드라면 3.0.0 메이저 버전 업데이트에 새로 포함된 버튼에 심긴 기능이었다.
그래서 똑같이 콘텐츠 다운로드를 해보는데, 다운로드 중 버튼을 다시 누르는 순간 앱이 종료되었다.
왜 이런 일이 생겼을까? 우리 앱에서는 버튼을 커스텀 클래스로 만들어서 사용하고 있는데, 이 버튼 인스턴스를 탭하는 순간 isHighlighted가 set되는 것이 원인이었다.
서브 뷰들 중 버튼들을 확인해 isHighlighted 속성을 변경하는데, 그 여러 버튼들 중 하나가 size가 (0, 0)이어서 UIGraphicsBeginImageContext로 이미지 컨텍스트를 만드는 데에서 에러가 난 것으로 보인다.
더 특이한 건 이 에러는 iOS 17 이상의 폰에서만 재현된다는 것이었다. 센트리에서도 os를 확인해보니 전부 iOS 17 이상이었고, iOS 15 테스트폰으로 테스트했을 때는 강종되지 않았고 iOS 17로 업데이트한 개인 폰에서만 강종됐다.
이걸 왜 몰랐을까 생각해보니, 개발하고 QA가 있었던 시기는 올해 5월로 아직 iOS 17이 출시되기 이전이었다. 그래서 현상이 발생할 수 있다는 사실조차도 몰랐던 것 같다.
검색해보니 Apple Developer Forums에 Xcode 15 시뮬레이터에서만 크래시가 발생한다는 글을 확인했는데, UIGraphicsBeginImageContext(_:) 문서로 넘어가보니 이미 Deprecated된 지 오래고, iOS 2.0-17.0 으로 기입되어 있었다.
에러 메시지에도 적혀 있듯, UIGraphicsBeginImageContext 대신 UIGraphicsImageRenderer를 쓰는 것이 해결법이 될 수 있다.
UIGraphicsImageRenderer는 iOS 10 이상부터 지원하고 있으므로 앞으로 이미지 조작을 위해 새로 코드를 작성할 때는 UIGraphicsBeginImageContext 대신 UIGraphicsImageRenderer를 사용하는 것이 더 안전할 것으로 예상된다.
원래 이미지에 알파값을 적용하는 코드는 이렇게 되어 있었는데,
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(at: .zero, blendMode: .normal, alpha: alpha)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage ?? UIImage()
이렇게 변경하면 된다.
return UIGraphicsImageRenderer(size: size).image { _ in
draw(at: .zero, blendMode: .normal, alpha: alpha)
}
그러면 iOS 17에서도 정상적으로 수행이 가능하다.
그런데 애초에 size가 (0, 0)인 버튼이 있지 않은데 왜 이런 이슈가 나왔는지 이해할 수가 없다…. 물론 어떤 케이스인지 찾아낼 수 있다면 size가 0이 되지 않도록 변경하거나 size를 검사해서 예외인 경우에는 따로 처리하는 방법도 있다.
하지만 이 경우에는 버튼을 찾아내는 것보다 extension 메소드를 변경하는 것이 더 옳은 방향이라고 생각했다. 그래야 혹시 모를 예외 케이스가 발생하더라도 강제종료되는 것은 막을 수 있을 것이다.
Image Resize와 관련해서 UIGraphicsBeginImageContext, UIGraphicsImageRenderer를 비교한 블로그 글을 봤다.
메모리 사용량과 연계해서 설명하신 점과 글 내용, 예제가 좋아서 링크한다.
[Swift] Image Resize
이미지가 커서 메모리를 많이 잡아먹다보면 메모리부족으로 앱이 죽어버리는 경우가 생기죠..! 이러한 현상을 줄이고자 이미지 사이즈를 줄여서 다시 만들어내는데 그 작업을 해볼까해요 이미
nsios.tistory.com