이미지 비동기, 캐싱 처리
Closed this issue · 4 comments
공식: Widget은 Async한 뷰를 지원하지 않는다. Apple Developer
현재 하고 있는 방식
View에서 Sync하게 이미지를 요청하고 다운이 완료되면 뷰에 출력된다.
단점: 느리다.. 느려.. 위젯은 반응이 느려도 된다 생각하지만 그래도 효율을 늘리고 싶다.
또 팀을 바꿔 볼 수도 있는거니까.. 여하튼 빨라져야한다..
애플이 내놓은 해결책
Timeline Provider에서 이미지 해결한 뒤에 Entry 넘기세요 ^^
말은 쉬운데 이렇게 하면 Provider와 Entry는 UI Framework를 가져야 한다...
그러면 image를 넘기지 말고 data를 넘기자
어쨋든 Provider에서 처리해주면 조금 더 빨라지지 않을까? ( 조금 일 것 같기도 하다 )
현재는 selectedTeamLogo의 다운이 완료되면 그제서야 oppositeTeamLogo의 다운이 시작 되고 이게 완료되야
뷰가 출력되는 형식이니 아무래도 동시 다운 이면 기대하는 바로는 2배 빨라 질것..?
Entry구조를 조금 더 단수하게 구조화했다.
그래서 Entry가 UIImage를 가지고 있도록 하게 해주었다.
Nextmatchup과 rank가 완료되면 home logo, away logo url을 추출해서 KingfisherManager.shared
를 이용해서 retreive 했다.
문제는 이미지가 두개가 모두 다운받고 나서야 Entry를 넘겨주고 싶었다.
그래서 Anypublisher를 completion에서 받게해서 다시 combineLatest를 이용하도록 하였다.
NextMatchUpTask().perform(nextMatchUpRequest).combineLatest(rankPublisher).sink { result in
switch result {
case .failure(let error):
completion(.failure(error))
return
case .finished: return
}
} receiveValue: { [weak self] (nextMatchUp, rank) in
guard let self = self,
let nextMatchUpData = nextMatchUp.response.first,
let rank = rank.response.first?.league.standings?.first?.first?.rank else {
completion(.failure(NetworkError.emptyData))
return
}
let imageDownloader = ImageDownloader()
let fixture = nextMatchUpData.fixture
let clubs = nextMatchUpData.teams
let league = nextMatchUpData.league
var homeLogo: AnyPublisher<UIImage, Never>?
imageDownloader.download(clubs.home.logo) { homeLogo = $0 }
var awayLogo: AnyPublisher<UIImage, Never>?
imageDownloader.download(clubs.away.logo) { awayLogo = $0 }
guard let homeLogo = homeLogo, let awayLogo = awayLogo else { return }
homeLogo.combineLatest(awayLogo)
.receive(on: DispatchQueue.main)
.sink { home, away in
let entry = NextMatchUpEntry(selected: clubID, rank: rank, fixture: fixture, clubs: clubs, league: league, homeLogo: home, awayLogo: away)
completion(.success(entry))
}.store(in: &self.cancelBag)
}.store(in: &cancelBag)
어쩔 수 없이 코드가 길어졌는데.. 문제는 더 느려졌다.
캐싱은 이루어지고 있는데 sync하게 받는것보다 느려졌다.
var homeLogo: AnyPublisher<UIImage, Never>?
imageDownloader.download(clubs.home.logo) { homeLogo = $0 }
var awayLogo: AnyPublisher<UIImage, Never>?
imageDownloader.download(clubs.away.logo) { awayLogo = $0 }
guard let homeLogo = homeLogo, let awayLogo = awayLogo else { return }
이 부분이 문제!
func download(_ url: String, completion: @escaping (AnyPublisher<UIImage, Never>)->()) {
guard let url = URL(string: url) else { return }
KingfisherManager.shared.retrieveImage(with: url) { result in
switch result {
case .success(let value):
completion(Just(value.image as UIImage).eraseToAnyPublisher())
case .failure(let error):
print(error)
}
}
}
이미지가 다운이 완료되면 publisher를 발행하므로 guard에 걸려서 return 하게 되고
실패 policy상 다시 timeline을 요청할때 이미지가 받아지는 시스템이라 느렸다..
func downloadWithPublisher(_ url: String) -> AnyPublisher<UIImage, Error> {
guard let url = URL(string: url) else { return Fail(error: NetworkError.invalidURL).eraseToAnyPublisher() }
let publisher = PassthroughSubject<UIImage, Error>()
KingfisherManager.shared.retrieveImage(with: url) { result in
switch result {
case .success(let value):
let image = value.image as UIImage
publisher.send(image)
case .failure(let error):
print(error)
}
}
return publisher.eraseToAnyPublisher()
}
Publisher를 발행해주어서 해결! 이미지 다운이 빨라졌다!