우리는 에피소드의 날짜를 잡고 세 번째 섹션을 작성하기 위해 노력하고 있습니다.
세 번째 섹션
마지막으로 두 번째 섹션인 정보 셀을 채우는 작업을 했습니다.
세 번째 섹션이 기억나지 않는다면 다음과 같이 파란색으로 칠하십시오. 파란색 카드 행이 있을 것입니다. 많은 에피소드가 만들어졌기에 릭 산체스의 경우 51개다.
final class RMCharacterEpisodeCollectionViewCell: UICollectionViewCell {
...
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .systemBlue
contentView.layer.cornerRadius = 8
}
...
}
에피소드 VM에는 현재 URL만 있습니다. 이 URL에는 각 에피소드에 대한 정보가 포함되어 있으며 이는 추상화를 위한 모델을 만들어야 함을 의미합니다.
VM에서 fetchepisode 메서드를 만들고 configure에서 내보내도록 설정해 보겠습니다.
final class RMCharacterEpisodeCollectionViewCell: UICollectionViewCell {
...
public func configure(with viewModel: RMCharacterEpisodeCollectionViewCellViewModel) {
viewModel.fetchEpisode()
}
}
final class RMCharacterEpisodeCollectionViewCellViewModel {
...
public func fetchEpisode() {
guard let url = episodeDataUrl,
let request = RMRequest(url: url) else {
return
}
RMService.shared.execute(request,
expecting: RMEpisode.self) { result in
}
}
}
여기서 테이블뷰나 콜렉션뷰의 셀에 대한 이해가 필요합니다. 셀이 대기열에서 제거될 때마다 구성 기능이 호출됩니다. 즉, Congifure로 따라잡을 때 셀을 통과할 때 호출이 계속되는 상황이 발생합니다. 물론 이것은 불필요한 작업입니다.
이를 해결하는 방법에는 두 가지가 있습니다.
- 플래그를 설정하면 이미 가져온 데이터는 처리되지 않습니다.
- 모든 회차 데이터를 받기 위한 요청을 선제적으로 전송(보내기)하도록 설계되었습니다. 그렇다면 일종의 백그라운드 작업입니다.
먼저 첫 번째 방법. 패치는 isFetching이라는 플래그를 사용하여 각 CellVM에 대해 한 번만 수행됩니다.
final class RMCharacterEpisodeCollectionViewCellViewModel {
...
private var isFetching = false
...
public func fetchEpisode() {
guard !isFetching else {
return
}
guard let url = episodeDataUrl,
let request = RMRequest(url: url) else {
return
}
isFetching = true
RMService.shared.execute(request,
expecting: RMEpisode.self) { result in
switch result {
case .success(let success):
print(String(describing: success.id))
case .failure(let failure):
print(String(describing: failure))
}
}
}
}
인쇄해보면 화면에서 카드가 dequeue될 때마다 ID가 인쇄되지 않고 일단 생성되면 인쇄되지 않는 것을 확인할 수 있습니다.
이제 VM은 모델과 함께 데이터를 인쇄하도록 보기에 알려야 합니다. 어떻게? 물론 지금까지 작성했던 델리게이트도 사용할 수 있습니다. 그러나 다른 방법이 있습니다. 새로운 방법을 시도해보자
final class RMCharacterEpisodeCollectionViewCellViewModel {
...
private var episode: RMEpisode?
...
public func fetchEpisode() {
...
RMService.shared.execute(request,
expecting: RMEpisode.self) { (weak self) result in
switch result {
case .success(let model):
self?.episode = model
...
}
}
}
}
게시자 및 구독자 패턴
- Pub Sub 패턴이라고도 합니다.
registerForData 메서드를 사용하여 게시합니다. 이 메서드에는 완료 기능이 있으며 이제 이를 블록이라고 합니다. Alfraz는 그것이 더 나은 용어라고 생각합니다.
다음으로 프로토콜이 생성되고 프로토콜은 필요한 데이터를 정의합니다. 다음으로 데이터를 등록할 때 모델이 아닌 로그를 직접 가져오고자 합니다.
이렇게 하면 숨겨진 동안 모델의 모든 정보를 유지할 수 있습니다.
블록은 전역 범위에서 수신 및 사용되어야 합니다. 여기서 매우 흥미로운 부분은 블록 자체의 유형을 (Protocol → Void)로 설정할 수 있다는 것입니다. 생각지도 못한 방법이었다.
다음으로 에피소드의 didset은 모델을 dataBlock(model)에 전달합니다. 모델이 성공적으로 전달되면 성공하면 백그라운드에서 데이터를 업데이트합니다.
protocol RMEpisodeDataRnder {
var name: String { get }
var airDate: String { get }
var episode: String { get }
}
final class RMCharacterEpisodeCollectionViewCellViewModel {
...
private var dataBlock: ((RMEpisodeDataRnder) -> Void)?
private var episode: RMEpisode? {
didSet {
guard let model = episode else {
return
}
dataBlock?(model)
}
}
...
public func registerForData(_ block: @escaping (RMEpisodeDataRnder) -> Void) {
self.dataBlock = block
}
public func fetchEpisode() {
...
RMService.shared.execute(request,
expecting: RMEpisode.self) { (weak self) result in
switch result {
case .success(let model):
DispatchQueue.main.async {
self?.episode = model
}
...
}
}
}
}
VM에서 작업이 완료되면 Cell에 표시합니다. 먼저 출력입니다.
final class RMCharacterEpisodeCollectionViewCell: UICollectionViewCell {
...
public func configure(with viewModel: RMCharacterEpisodeCollectionViewCellViewModel) {
viewModel.registerForData { data in
print(String(describing: data))
}
viewModel.fetchEpisode()
}
}
이제 이 구조를 가로지르면 경계선의 경우가 발생합니다.
극단적인 경우는 가져오기를 통해 이미 데이터를 가져온 상태에서 데이터를 셀로 가져오는 경우입니다.
이 문제를 해결하기 위해 모델을 보호자에게 반환하기 전에 dataBlock으로 전달합니다.
public func fetchEpisode() {
guard !isFetching else {
if let model = episode {
self.dataBlock?(model)
}
return
}
...
}
Protocol, DidSet, DataBlock으로 델리게이트를 사용하는 것과 동일한 기능을 수행하는 테마를 구현해봤습니다.
Cell View에서 viewModel을 통해 접근 가능한 데이터를 보면 log에 name, airDate, episode만 정의되어 있는 것을 확인할 수 있다. 이를 통해 효과적으로 데이터를 숨길 수 있습니다.
여기서 우리가 한 모든 작업을 기억한다면 탭 표시줄에 “에피소드” 탭이 있음을 알 수 있습니다. 그런데 생각해보면 에피소드 탭에 있는 패치를 통해 에피소드 정보를 불러와야 할까요? 왜 두 번 소통해야 합니까?
존재하지 않습니다. 나중에 세션을 캐싱하거나 DB 또는 Core Data를 통해 데이터를 저장하면 해결됩니다.
끝