dataTaskPublisher() ๋ URLSession ์ ์ํด์๋ ์ปด๋ฐ์ธ ์ฐ์ฐ์๋ก ๋คํธ์ํฌ ์๋ต์ Publisher ๋ฅผ ํตํด ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋ค. dataTaskPublisher(for:) ๋ฉ์๋๋ URL ์์ฒญ์ ๋ฐ์์ ์คํํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ Publisher ํํ๋ก ๋ฐํํฉ๋๋ค. ๋ฐํ๋ Publisher๋ ๋ค์๊ณผ ๊ฐ์ ํํ ํํ์ ์ถ๋ ฅ์ ๋ด๋ณด๋ธ๋ค -> (data, response)
- data: ์๋ฒ๋ก๋ถํฐ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์๋ Data ๊ฐ์ฒด
- response: ์์ฒญ์ ๋ํ ์๋ต ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ URLResponse ๊ฐ์ฒด
์ปด๋ฐ์ธ์์ Publishers๋ ๊ตฌ์กฐ์ฒด๋ก ์ ์๋์ด ๊ฐ์ผ๋ก ์ ๋ฌ๋๋ฉฐ, ๊ฐ๊ฐ์ ๋ณต์ฌ๋ณธ์ด ๋ ๋ฆฝ์ ์ผ๋ก ์์ ์ ์ํํ๋ค. ์ฌ๊ธฐ์ share() ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ํ๋์ด ๋ณ๊ฒฝ๋์ด ์ฌ๋ฌ ๊ตฌ๋ ์๊ฐ ๋์ผํ ์์ ๊ฒฐ๊ณผ๋ฅผ ๊ณต์ ํ ์ ์๊ฒ ๋๋ค. share()๋ Publisher๋ฅผ ์ฐธ์กฐ ํํ๋ก ๊ด๋ฆฌํ๊ฒ ํ์ฌ ์ฌ๋ฌ ๊ตฌ๋ ์๊ฐ ๋์ผํ Publisher ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๋๋ก ํ๋ค. ์ด๋ ๋คํธ์ํฌ ์์ฒญ ๊ฐ์ ๋น์ฉ์ด ๋ง์ด ๋๋ ์์ ์ ์ค๋ณตํด์ ์ํํ์ง ์๋๋ก ๋์์ค๋ค.
์ด๋ ์ฃผ์ํ ์ ์ share() ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ Publisher๋ฅผ Lazy๋ก ์ ์ธํด์ผ ํ๋ค! lazy๋ก ์ ์ธ๋ Publisher์ ํจ๊ป ์ฌ์ฉ๋ ๋, share()๋ ์ฒซ ๊ตฌ๋ ์์ ์๋ง Publisher๋ฅผ ํ์ฑํํ๊ณ , ์ดํ ๋ชจ๋ ๊ตฌ๋ ์์๊ฒ ๋์ผํ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ ๊ณตํ๋ค! lazy๋ก ์ ์ธํ์ง์๊ณ ๊ทธ๋ฅ Publisher๋ฅผ ์ ์ธํ ๊ฒฝ์ฐ ๊ฐ ์ธ์คํด์ค๊ฐ ์๋ก๋ค๋ฅธ Publisher๋ฅผ ์ฐธ์กฐํ๊ฒ๋๋ค!!
๋ฐฑ๋ฌธ์ด๋ถ์ฌ์ผ๊ฒฌ์ด๋ค. ๊ฐ๋ ์ค๋ช ์ด ๋๋ฌ์ผ๋ ์์ ์ฝ๋๋ฅผ ํตํด ์ดํด๋ณด์
import Combine
import Foundation
protocol BoxOfficeRepository {
var boxOfficeDataPublisher: AnyPublisher<BoxOfficeDTO, NetworkError> { get }
}
class BoxOfficeRepositoryImpl: BoxOfficeRepository {
private let urlString = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=f5eef3421c602c6cb7ea224104795888&targetDt=20240101"
lazy var boxOfficeDataPublisher: AnyPublisher<BoxOfficeDTO, NetworkError> = {
guard let url = URL(string: urlString) else {
fatalError("Invalid URL")
}
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: BoxOfficeDTO.self, decoder: JSONDecoder())
.mapError { error -> NetworkError in
switch error {
case is URLError:
return .urlError
case is DecodingError:
return .decodingError
default:
return .sessionFailed(error: error)
}
}
.share()
.eraseToAnyPublisher()
}()
}
class BoxOfficeViewModel {
private let repository: BoxOfficeRepository
private var cancellables: Set<AnyCancellable> = []
init(repository: BoxOfficeRepository) {
self.repository = repository
}
func loadData() {
repository.boxOfficeDataPublisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed successfully")
case .failure(let error):
print("Error occurred: \(error.localizedDescription)")
}
}, receiveValue: { boxOfficeDTO in
print("Box Office Type: \(boxOfficeDTO.boxOfficeResult.boxofficeType)")
print("Show Range: \(boxOfficeDTO.boxOfficeResult.showRange)")
for movie in boxOfficeDTO.boxOfficeResult.dailyBoxOfficeList {
print("Rank: \(movie.rank) | Movie Name: \(movie.movieName) | Sales Amount: \(movie.salesAmount)")
}
})
.store(in: &cancellables)
}
}
class BoxOfficeViewModel2 {
private let repository: BoxOfficeRepository
private var cancellables: Set<AnyCancellable> = []
init(repository: BoxOfficeRepository) {
self.repository = repository
}
func loadData() {
repository.boxOfficeDataPublisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("ViewModel2: Completed successfully")
case .failure(let error):
print("ViewModel2: Error occurred: \(error.localizedDescription)")
}
}, receiveValue: { boxOfficeDTO in
print("ViewModel2: Box Office Type: \(boxOfficeDTO.boxOfficeResult.boxofficeType)")
print("ViewModel2: Show Range: \(boxOfficeDTO.boxOfficeResult.showRange)")
for movie in boxOfficeDTO.boxOfficeResult.dailyBoxOfficeList {
print("ViewModel2: Rank: \(movie.rank) | Movie Name: \(movie.movieName) | Sales Amount: \(movie.salesAmount)")
}
})
.store(in: &cancellables)
}
}
// Usage
let repository = BoxOfficeRepositoryImpl()
let viewModel1 = BoxOfficeViewModel(repository: repository)
let viewModel2 = BoxOfficeViewModel(repository: repository)
viewModel1.loadData()
viewModel2.loadData()
(์์ ์ฝ๋์์ DTO, Custom Error ๊ฐ์ฒด๋ ์๋ตํ๋ค)
์ ์ฒด์ ์ธ ๊ตฌ์กฐ๋ ํ๋ ์ ํ ์ด์ ๋ ์ด์ด์ ๋ทฐ๋ชจ๋ธ ๊ฐ์ฒด๊ฐ ๋ฐ์ดํฐ ๋ ์ด์ด์ ๋ ํฌ์งํ ๋ฆฌ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ๋ค์ด๋ค. ์ด๋ ๋์จํ ๊ด๊ณ๋ฅผ ์ํด Protocol ์ธํฐํ์ด์ค๋ฅผ ๋์๋ค. ์ด๋ ๋ฉ์๋๋ก ๋์ง ์๊ณ ์ฝ๊ธฐ ์ ์ฉ์ผ๋ก ๋ ๊ฒ์ ์ฐธ์กฐ๋ฅผ ์ ์งํด์ผ publisher ๊ณต์ ๊ฐ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
share ์ฐ์ฐ์์ ๊ฐ์ด subscriber๋ค์ด publisher๋ฅผ ๊ณต์ ํ๋ ์ฐ์ฐ์๋ก multicast() ์ฐ์ฐ์๊ฐ ์๋ค. ๋ ์ฐ์ฐ์ ๋ชจ๋ ๋์ผํ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ฌ๋ฌ ๊ตฌ๋ ์์๊ฒ ์ ๋ฌํ ์ ์์ง๋ง ์ฐจ์ด์ ์ multicast๋ Subject๋ฅผ ๋ฐ์์ ์ด๋ฒคํธ ํธ๋ฆฌ๊ฑฐ์ ๋ฐฉ์ถ์ ๋ชจ๋ ํ ์ ์๊ธฐ ๋๋ฌธ์ ์๋น์ ์ธก์์ ๊ณต์ ์์ ์์ ์ ์ ํด์ค ์ ์๋ค.
share ์ multicast์ ์ฐจ์ด๋ฅผ 3๊ฐ์ง๋ก ์ ๋ฆฌํ ์ ์๋ค.
- ์ ์ด์ ์์ค: multicast๋ connect()๋ฅผ ํตํด ๋ฐํ์ ๋ช ์์ ์ผ๋ก ์์ํด์ผ ํ๋ฏ๋ก ๋ ๋ง์ ์ ์ด๊ฐ ๊ฐ๋ฅํ๋ค. ๋ฐ๋ฉด, share๋ ์๋์ผ๋ก ๊ตฌ๋ ๊ณผ ๋ฐํ์ด ์์๋๋ฏ๋ก ์ ์ด๊ฐ ๋ ํ์ํ๋ค.
- ์ฌ์ฉ์ ์ฉ์ด์ฑ: share๋ ๊ตฌํ์ด ๊ฐ๋จํ๋ฉฐ, ์ง์ Subject๋ฅผ ๊ด๋ฆฌํ ํ์๊ฐ ์๋ค. multicast๋ ์ฌ์ฉ์๊ฐ Subject๋ฅผ ์ค์ ํ๊ณ ๊ด๋ฆฌํด์ผ ํ๋ฏ๋ก ๋ณต์ก๋๊ฐ ๋์์ง๋ค.
- ์ฑ๋ฅ๊ณผ ์์ ๊ด๋ฆฌ: multicast๋ connect() ํธ์ถ ์ ๊น์ง๋ ์๋ฌด๋ฐ ์์๋ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ ์์ ์ฌ์ฉ์ ์ต์ ํํ ์ ์๋ค. share๋ ์๋์ผ๋ก ๊ด๋ฆฌ๋๊ธฐ ๋๋ฌธ์, ํน์ ๊ฒฝ์ฐ์๋ ๋นํจ์จ์ ์ผ ์ ์๋ค.
import Combine
// ์ด๋ฒคํธ๋ฅผ ๋ฐํํ subject ์์ฑ
let subject = PassthroughSubject<Int, Never>()
// multicast๋ก ์๋ก์ด PassthroughSubject๋ฅผ ๋ง๋ฆ
let multicasted = subject
.multicast(subject: PassthroughSubject<Int, Never>())
// ๊ตฌ๋
์๋ค์ ์ถ๊ฐ
let subscriber1 = multicasted.sink(
receiveCompletion: { print("Subscriber 1 completed: \($0)") },
receiveValue: { print("Subscriber 1 received: \($0)") }
)
let subscriber2 = multicasted.sink(
receiveCompletion: { print("Subscriber 2 completed: \($0)") },
receiveValue: { print("Subscriber 2 received: \($0)") }
)
// multicast ์ฐ๊ฒฐ ์์
let connection = multicasted.connect()
// ์ด๋ฒคํธ ๋ฐํ
subject.send(1)
subject.send(2)
subject.send(3)
// ์ด๋ฒคํธ ์คํธ๋ฆผ ์๋ฃ
subject.send(completion: .finished)
// ๊ตฌ๋
์ทจ์
connection.cancel()
subscriber1.cancel()
subscriber2.cancel()
์ ์ฝ๋์ฒ๋ผ connect() ํธ์ถ ์ ํตํด ์ด๋ฒคํธ ์คํธ๋ฆผ์ ์ค์ ๋ก ๊ตฌ๋ ์์๊ฒ ์ ๋ฌํ๋๋ก ์ด๋ฒคํธ ๊ตฌ๋ ํ์ฑํ ์์ ์ ์ ํด์ค ์ ์๋ค.
https://www.kodeco.com/21773708-intermediate-combine/lessons/1
'๐ฆ Flutter' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| Combine | 11. Mapping Errors (0) | 2024.05.14 |
---|---|
| Combine | 10. Managing Backpressure (0) | 2024.05.14 |
| Combine | 8. Sequencing Operators (0) | 2024.05.13 |
| Combine | 7. Scheduling Operators (0) | 2024.05.13 |
Swift์์ CustomStringConvertible ํ๋กํ ์ฝ ํ์ฉํ๊ธฐ (0) | 2024.05.13 |