서버에서 가져온 데이터를 화면에 표시
영화 데이터를 서버에서 요청하여 받아오는 작업을 할 때,
한 화면에서 여러 개의 데이터를 요청해야 하는 경우가 있습니다.
예를 들어, 해리포터와 관련된 영화 목록이나
트랜스포머와 관련된 영화 목록과 같은
영화 목록 데이터를 여러 개 받아오는 경우가 있습니다.
서버에서 받아온 데이터를 화면에 보여줄 때,
CollectionView나 TableView를 통해 보여주게 됩니다.
이때, 언제 reloadData()
메서드를 호출해 뷰를 갱신시켜줄지에 대해 고민하게 됩니다.
예시 화면과 코드
var movieInfo1: [MovieResult] = []
var movieInfo2: [MovieResult] = []
var movieInfo3: [MovieResult] = []
var movieInfo4: [MovieResult] = []
override func viewDidLoad() {
movieManager.requestRecommendation(movieID: 100) { data in
self.movieInfo1 = data.results
self.posterCollectionView.reloadData()
}
movieManager.requestRecommendation(movieID: 101) { data in
self.movieInfo2 = data.results
self.posterCollectionView.reloadData()
}
movieManager.requestRecommendation(movieID: 102) { data in
self.movieInfo3 = data.results
self.posterCollectionView.reloadData()
}
movieManager.requestRecommendation(movieID: 103) { data in
self.movieInfo4 = data.results
self.posterCollectionView.reloadData()
}
}
코드 설명
서버에 movieID를 요청하면 영화와 관련된 목록을 보내주게됩니다.
그러면 받아온 목록을 movieInfo1 ~ 4 Array에 넣어주고
CollectionView
의 데이터를 reloadData()
메서드를 호출해 갱신해줍니다.
movieManager.requestRecommendation(movieID:)
는 DispatchQueue.global().async
로 작동하게 됩니다.
뒤에 따라오는 클로저는 DispatchQueue.main.async
로 작동하게 됩니다.
reloadData() 호출 횟수를 줄일 수는 없을까?
위 코드에서는 reloadDate()
메서드를 반복해서 사용하고 있습니다.
그러면 모든 요청을 완성했을 때 한 번만 reloadData()
를 사용하면 되지 않을까? 라는 생각을 하게됩니다.
무시무시한 콜백 지옥…
movieManager.requestRecommendation(movieID: 101) { data in
self.movieInfo1 = data.results
self.movieManager.requestRecommendation(movieID: 102) { data in
self.movieInfo2 = data.results
self.movieManager.requestRecommendation(movieID: 103) { data in
self.movieInfo3 = data.results
self.movieManager.requestRecommendation(movieID: 104) { data in
self.movieInfo4 = data.results
self.posterCollectionView.reloadData()
}
}
}
}
모든 요청을 마무리 하고 난 뒤에 데이터를 갱신하기 위해서는 요청이 완료된 시점에 요청을 추가하고, 그 요청이 완료된 시점에 요청을 추가하는 방식을 사용하게 되면 위와 같은 콜백 지옥에 빠지게 됩니다.
그렇다면 어떻게 하면 콜백 지옥을 만들지 않으면서 모든 요청이 마무리 됐을 때 데이터를 갱신할 수 있을까요?
DispatchGroup
DispatchGroup을 활용하면 비동기 작업을 group으로 묶어주고
group의 작업이 끝났을 때를 알려주고 알려준 시점에 원하는 작업을 추가하는 방법을 사용할 수 있습니다.
코드
let group = DispatchGroup()
DispatchQueue.global().async(group: group) {
self.movieManager.requestRecommendation(movieID: 101) { data in
self.movieInfo1 = data.results
}
}
DispatchQueue.global().async(group: group) {
self.movieManager.requestRecommendation(movieID: 102) { data in
self.movieInfo2 = data.results
}
}
DispatchQueue.global().async(group: group) {
sleep(1)
self.movieManager.requestRecommendation(movieID: 103) { data in
self.movieInfo3 = data.results
}
}
DispatchQueue.global().async(group: group) {
sleep(1)
self.movieManager.requestRecommendation(movieID: 104) { data in
self.movieInfo4 = data.results
}
}
group.notify(queue: .main) {
self.posterCollectionView.reloadData()
}
코드 실행 화면
하지만 추가적인 문제점 발생
위 코드를 보면 정상적으로 동작할 것 같지만 한 가지 문제점이 있습니다.
DispatchQueue.global().async(group: group) {
sleep(1)
self.movieManager.requestRecommendation(movieID: 103) { data in
self.movieInfo3 = data.results
}
}
바로 비동기 코드 안에 또 다른 비동기 코드가 있기 때문에 작업이 언제 끝나는지 파악하기 어렵습니다.
self.movieManager.requestRecommendation(movieID: 103)
요청 메서드는 비동기 코드이므로 작업을 다른 스레드로 넘기고 바로 제어권을 반환합니다.
DispatchQueue.global().async(group: group)
은 위 요청의 완료 여부와 상관 없이 즉시 작업이 완료되었다고 인식합니다.
그 결과, 의도와는 다르게 2번째 섹션과 3번째 섹션은 데이터가 들어오기 전에 reloadData()
메서드가 호출되어 화면에 정상적으로 표시되지 않는 문제가 발생합니다.
enter(), leave()
enter()
, leave()
메서드를 활용하면 문제를 해결할 수 있는데요.
코드
let group = DispatchGroup()
group.enter() // group reference Count +1
self.movieManager.requestRecommendation(movieID: 101) { data in
self.movieInfo1 = data.results
group.leave() // group reference Count -1
}
group.enter() // group reference Count +1
self.movieManager.requestRecommendation(movieID: 102) { data in
self.movieInfo2 = data.results
group.leave() // group reference Count -1
}
group.enter() // group reference Count +1
self.movieManager.requestRecommendation(movieID: 103) { data in
self.movieInfo3 = data.results
group.leave() // group reference Count -1
}
group.enter() // group reference Count +1
self.movieManager.requestRecommendation(movieID: 104) { data in
self.movieInfo4 = data.results
group.leave() // group reference Count -1
}
group.notify(queue: .main) {
self.posterCollectionView.reloadData()
}
비동기 메서드를 시작할 때 group.enter()
메서드를 호출해 group의 Reference Count를 올려줍니다.
그리고 작업이 완료되면 group.leave()
메서드를 호출해 group의 Reference Count를 내려줍니다.
group.notify()
에서 group의 Reference Count를 추적해 +- 가 0이 되면 클로저를 실행하게 됩니다.
Uploaded by
N2T