ghis22130/footballget

이미지 비동기, 캐싱 처리

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를 발행해주어서 해결! 이미지 다운이 빨라졌다!