Taehyeon-Kim / Taehyeon-Kim.github.io

The life of recording is unbreakable
https://taehyeon-kim.github.io
MIT License
1 stars 0 forks source link

Combine101: Eliminate duplicate, concurrent network calls #6

Open Taehyeon-Kim opened 10 months ago

Taehyeon-Kim commented 10 months ago
import Foundation
import Combine

struct Article: Decodable {
  let title: String
  let description: String
  let author: String
}

/// Duplicate, concurrent network calls
class ArticleLoader {
  typealias Publisher = AnyPublisher<Article, Error>

  private let queue = DispatchQueue(label: "ArticleLoader")

  private let urlSession: URLSession
  private let decoder: JSONDecoder

  private var publishers = [URL: Publisher]()

  init(urlSession: URLSession = .shared, decoder: JSONDecoder = .init()) {
    self.urlSession = urlSession
    self.decoder = decoder
  }

  /// 해당 메서드가 동일한 URL로 병렬 또는 빠르게 연속으로 여러번 호출된다고 가정
  /// 메서드를 호출할때마다 새로운 퍼블리셔 생성, 이 경우 네트워크 요청 중복 발생
  func loadArticles(from url: URL) -> AnyPublisher<Article, Error> {
    if let publisher = publishers[url] {
      return publisher
    }

    let publisher = urlSession
      .dataTaskPublisher(for: url)
      .map(\.data)
      .decode(type: Article.self, decoder: decoder)
      .receive(on: queue)
      /// 이전 publisher가 메모리에 남아있지 않도록 함, 완료되면 dictionary에서 publisher를 제거
      .handleEvents(receiveCompletion: { [weak self] _ in
        self?.publishers[url] = nil
      })
      .share()
      .eraseToAnyPublisher()

    publishers[url] = publisher
    return publisher
  }
}
  1. dictionary 사용
  2. .share() 연산자 사용
  3. serial queue 사용(for thread-safe)

Links: