처음 보는 UIImageView.image must be used from main thread only
보라색 에러가 나왔다.
의미를 찾아보니, UIImageView의 image
속성은 반드시 메인 스레드에서 사용해야 한다는 의미였는데,
이 오류는 비동기 작업을 사용하지 않거나,
메인 스레드가 아닌 다른 스레드에서 UIImageView
의 image
속성을 수정하려 할 때 발생한다고 한다.
.
.
UIKit은 UI 관련 작업을 메인 스레드에서만 처리하도록 설계되어 있다.
따라서 UIImageView
나 다른 UI 요소의 속성을 변경하려면 반드시 메인 스레드에서 해야 하는데,
비동기 작업에서 데이터를 처리하는 경우에는 작업이 백그라운드 스레드에서 수행될 수 있기 때문에 메인 스레드에서 UI를 업데이트해야 한다는 점을 기억해야 한다.
만약 UIImageView.image
를 비동기 작업 중 백그라운드 스레드에서 설정하려고 하면,
UIImageView.image must be used from main thread only
오류가 발생할 수 있는 것이다.
UIImageView.image
를 설정할 때 메인 스레드에서만 접근해야 하기 때문에,
비동기 작업 내에서 UIImageView
의 image
속성을 설정할 때
DispatchQueue.main.async
를 사용해야 한다.
이걸 통해 메인 스레드에서 UI를 업데이트할 수 있도록!
수정 전 코드)
if let data = try? Data(contentsOf: imageUrl), let image = UIImage(data: data) {
completion(image)
} else {
completion(nil)
}
이 코드는 동기 실행 방법이다.
Data(contentsOf:)
를 사용해서 이미지를 다운로드하는 작업이 메인 스레드에서 동기적으로 실행되고 있었다.
네트워크 요청은 시간이 오래 걸리는 단점이 있고,
메인 스레드에서 동기적으로 실행하면 UI가 멈추는 문제가 발생한다고 한다.
이미지 다운로드는 네트워크 요청이기에 메인 스레드에서 실행하면 앱의 반응성도 떨어지는건 덤이었고,
때문에 사용자는 앱이 멈춘 것처럼 느낄 수 있어서 아주 좋지 않은 코드임을 알게 됐다.
심지어 이 방식은 네트워크 작업을 메인 스레드에서 처리하기 때문에 별로 권장되지 않는 방식이었다..
.
.
.
수정 후 코드)
DispatchQueue.global().async {
if let data = try? Data(contentsOf: imageUrl), let image = UIImage(data: data) {
DispatchQueue.main.async {
completion(image)
}
} else {
DispatchQueue.main.async {
completion(nil)
}
}
}
수정 후 코드는 비동기 실행 방법이다.
DispatchQueue.global().async
를 사용해서 이미지 다운로드 작업을 백그라운드 스레드에서 비동기적으로 실행했다.
네트워크 작업은 시간이 오래 걸릴 수 있기 때문에,
이걸 메인 스레드가 아닌 백그라운드 스레드에서 실행하는게 좋다.
그리고 이미지를 다운로드한 후에는 UI를 업데이트해야 하는데,
UI 변경은 반드시 메인 스레드에서 이루어져야 한다.
그래서 DispatchQueue.main.async
를 사용해 UI 업데이트를 메인 스레드에서 처리하게끔 해결했다.
이렇게 하면 네트워크 작업이 메인 스레드를 차단하지 않아서 앱의 반응성도 유지가 될 뿐더러 사용자 경험도 좋아지고, 앱의 성능 문제 또한 피할 수 있다는 장점이 있다.
이렇게 콜백까지 다루게 될 줄이야...
특징 | 첫 번째 코드 | 두 번째 코드 |
---|---|---|
실행 방식 | 동기적으로 실행 (메인 스레드) | 비동기적으로 실행 (백그라운드 스레드) |
네트워크 작업 위치 | 메인 스레드에서 처리 | 백그라운드 스레드에서 처리 |
UI 멈춤 가능성 | 있음 (UI가 멈추거나 끊김 현상 발생 가능) | 없음 (백그라운드에서 실행, UI는 원활) |
권장 여부 | 비권장 | 권장 |
네트워크 요청과 같은 시간이 오래 걸릴 수 있는 작업은 항상 비동기적으로 실행하는 것이 좋다.
두번째 코드처럼 DispatchQueue.global().async
를 사용해서 네트워크 작업을 백그라운드에서 처리해주고,
UI 업데이트는 DispatchQueue.main.async
를 사용해 메인 스레드에서 처리하는 것이 최적의 방법이었다.
이미지도 랜덤으로 잘 나온다!
이번 문제를 통해 비동기 작업과 UI 업데이트의 중요성을 다시 한번 깨달았다.
네트워크 작업과 같이 시간이 오래 걸릴 수 있는 코드는 메인 스레드가 아닌 백그라운드 스레드에서 실행해야 하고,
UI 업데이트는 반드시 메인 스레드에서 이루어져야 한다는 원칙을 확실히 이해하게 된 것 같다.
앞으로 네트워크와 관련된 작업을 구현할 때는 반드시 비동기 처리를 생각하면서 설계하겠다는 다짐을 해본다.
또 다시 같은 문제를 다시 겪지 않기 위해 글을 썼지만..
한번 더 겪게 된다면 다시 이 글을 보러오면 되니까 화이팅
노란색 피 : 바로 수정 가능 그냥 괜찮음
빨간색 피 : 고치면 되니까 이것도 나름 괜찮음
보라색 피 : 이거 뭐야 나 이제 죽는 건가?