wift ์ ๋ฆฌ - URLSession + Combine
์ถ์ฒ: Processing URL Session Data Task Results with Combine - Apple Developer
๋น๋๊ธฐ ์ฐ์ฐ์๋ค์ ์ฌ์ฉํด URL๋ก๋ถํฐ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค.
๋คํธ์ํฌ ์์
์ ๋ณธ์ง์ ์ผ๋ก ๋น๋๊ธฐ ์์
์ด๊ณ , ์ด๋ฌํ ๋น๋๊ธฐ ์์
๋ค์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์๋ค.
Combine ๋ํ ๋น๋๊ธฐ๋ฅผ ์ฒ๋ฆฌํ๋ ํ๋ ์์ํฌ์ด๋ฏ๋ก, ์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋คํธ์ํฌ ์์
์ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
dataTaskPublisher(for:)
URLSession
์ URL
ํน์ URLRequest
๋ก๋ถํฐ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ publishํ๊ธฐ ์ํ URLSession.DataTaskPublisher
๋ผ๋ Combine publisher์ ์ ๊ณตํ๋ค.
dataTaskPublisher(for:)
๋ฉ์๋๋ฅผ ํตํด publisher์ ์์ฑํ ์ ์๊ณ , ์ด๋ ๋ค์ ๋ ๊ฐ์ง๋ฅผ ๋ฐฉ์ถํ ์ ์๋ค.
- task ์ฑ๊ณต ์
data์URLResponse
๋ก ์ด๋ฃจ์ด์ง ํํ - task ์คํจ ์
error
๊ธฐ์กด dataTask(with:completionHandler:)
์๋ ๋ค๋ฅด๊ฒ, publisher์ด ์ต์
๋์ ํด์ ํด ์ค๋๋ค.
๋ํ completion handler์ ์ฌ์ฉํ ์ฝ๋๋ ๊ด๋ จ ์์ ๋ค์ ์ ๋ถ ๋ค completion handler ํด๋ก์ ๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ํ์ง๋ง publisher์ ์ฌ์ฉํ๋ค๋ฉด ํด๋ก์ ์ ์๋ง์ ์์ ๋ค์ Combine ์ฐ์ฐ์๋ค๋ก ๋์ฒดํ ์ ์์ต๋๋ค.
Raw Data -> Custom Type
๋คํธ์ํฌ ์์
์ ๋ง์น๋ฉด Data
ํ์
์ ๊ฐ์ด ์ ๋ฌ๋ฉ๋๋ค.
Combine์ Data
์์ Custom Type
์ผ๋ก ๋ณํํ๋ ์์
์ ์ฐ์ฐ์๋ค๋ก ์ง์ํด์ค๋๋ค.
์์์ ์ธ๊ธํ ๋๋ก, task ์ฑ๊ณต ์ Data
์ URLResponse
๊ฐ ์ ๋ฌ๋๋๋ฐ, map(_:)
๋๋ tryMap(_:)
์ผ๋ก ํ์
์ ๋ณ๊ฒฝํด์ค ์ ์์ต๋๋ค.
Data
๋ฅผ Custom Type
์ผ๋ก ๋ณ๊ฒฝํ๊ธฐ ์ํด
(Custom Type
์ Decodable
์ ์ฑํํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.)
Combine์ decode(type:decoder:)
์ ์ฌ์ฉํฉ๋๋ค.
๋ค์์ URL์์ ๋ฐ์ json ๋ฐ์ดํฐ๋ฅผ User
ํ์
์ผ๋ก ๋ณํํ๋ ๊ณผ์ ์
๋๋ค.
struct User: Codable {
let name: String
let userID: String
}
let url = URL(string: "https://example.com/endpoint")!
cancellable = urlSession
.dataTaskPublisher(for: url)
.tryMap() { element -> Data in
guard let httpResponse = element.response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return element.data
}
.decode(type: User.self, decoder: JSONDecoder())
.sink(receiveCompletion: { print ("Received completion: \($0).") },
receiveValue: { user in print ("Received user: \(user).")})
-
dataTaskPublisher(for: url)
๋ก ๋คํธ์ํฌ ์์ ์ ์์ํฉ๋๋ค. -
tryMap() { element -> Data in }
์ผ๋ก ์๋dataTaskPublisher
์ด ๋ฐฉ์ถํ๋ ํํ์ธ (Data
,Response
)์ ๊ฒ์ฌํ๊ณ , ์ํ๋ ํํ๋ก ๊ฐ๊ณตํฉ๋๋ค. -
decode(type: User.self, decoder: JSONDecoder())
์ผ๋กtryMap()
์์ ๋ด๋ ค์จ data๋ฅผ Userํ์ ์ผ๋ก ๋ณํํฉ๋๋ค.
Retry + Error Handling
๋คํธ์ํฌ ์์
์ ์๋ง์ ์๋ฌ๊ฐ ์์๋๋ ์์
์
๋๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ์ ์ฑ์ ์๋ฌ๋ฅผ ํ๋ฅญํ ์ฒ๋ฆฌํด์ค์ผ ํ ํ์๊ฐ ์์ต๋๋ค.
๊ฒฝ์ฐ์ ๋ฐ๋ผ์ ์คํจํ ์์
์ ์ฌ์๋ํด์ผํ๋ ์ํฉ์ด ํ์ํ ์ ์์ต๋๋ค.
completion handler์ ์ฌ์ฉํ๋ค๋ฉด, ์ฌ์๋๋ฅผ ์ํด handler์ ๋ค์ ์์ฑํด์ผ ํฉ๋๋ค.
retry(_:)
Combine์ retry(_:)
ํ ์ค๋ก ๊ฐ๋ฅํฉ๋๋ค.
์๋ฌ๋ฅผ ๋ฐ์ผ๋ฉด upstream publisher์ ๋ํ ๊ตฌ๋
์ ์ง์ ๋ ํ์๋งํผ ๋ค์ ์์ฑํฉ๋๋ค.
ํ์ง๋ง ๋คํธ์ํฌ ์์ ์ด ๋น์ฉ์ด ๋ง์ด ๋๋ ์์ ์ด๋ฏ๋ก ๋๋ฌด ๋ง์ด ์ฌ์๋๋ฅผ ์์ฒญํ๋๊ฒ์ ์ข์ง ์์ต๋๋ค. ๋ํ ๋ชจ๋ ์์ฒญ์ด ์ ํจํ์ง ํ์ธํด์ผ ํฉ๋๋ค.
catch(_:)
์๋ฌ๋ฅผ ๋ค๋ฅธ publisher๋ก ๋ณํํฉ๋๋ค.
fallback URL์์ ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํ๋ ๊ฒ๊ณผ ๊ฐ์ด, ๋ค๋ฅธ URLSession.DataTaskPublisher
๊ณผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
replaceError(with:)
๊ฐ๋ฐ์๊ฐ ์ ๊ณตํ ๋ด์ฉ์ผ๋ก ์๋ฌ๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
์๋ฌ๋ฅผ ๋ค๋ฅธ ๋ด์ฉ์ผ๋ก ๋ณ๊ฒฝํ์ฌ, ์ฑ๊ณตํ์ ๋์ ๋์ผํ ์ฒ๋ฆฌ๋ฅผ ์คํํ ์๋ ์์ต๋๋ค.
let pub = urlSession
.dataTaskPublisher(for: url)
.retry(1)
.catch() { _ in
self.fallbackUrlSession.dataTaskPublisher(for: fallbackURL)
}
cancellable = pub
.sink(receiveCompletion: { print("Received completion: \($0).") },
receiveValue: { print("Received data: \($0.data).") })
retry(1)
๋ก, ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด upstream publisher์ ๋ํ ๊ตฌ๋ ์ ํ ๋ฒ ๋ค์ ์์ฑํฉ๋๋ค.catch()
๋ก, ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉดfallBackUrlSession
์ ํธ์ถํ ์ ์์ต๋๋ค.
ํด๋น ๊ตฌ๋ฌธ์ด ์คํ๋๋ ๊ฒ์retry(1)
์ ํ ๋ฒ ๊ฑฐ์น ํ ์คํ๋๋ค๋ ๊ฒ์ ์๋ฏธํ๋ฏ๋ก, ๋ ๋ฒ์งธerror
์ด๋ผ๊ณ ํ ์ ์์ต๋๋ค.
Dispatch Queue + Scheduling Operators
URLSession
์ completion handler๋ก ์ฒ๋ฆฌํ ๋์๋ handler ๋ด๋ถ์์ ์ง์ Dispatch Queue ๋ฑ์ ์ฌ์ฉํ์ฌ ํน์ ํ๋ก ์์
์ ์ฎ๊ฒจ์ผ ํ์ต๋๋ค.
receive(on:options:)
๋ฉ์๋๋ก ํด๋น ๋ฉ์๋ ์ดํ์ subscriber์ operator์ ์์
ํ๋ฅผ ์ง์ ํด์ค ์ ์์ต๋๋ค.
DispatchQueue์ RunLoop ๋ชจ๋ Combine์ Scheduler
ํ๋กํ ์ฝ์ ๊ตฌํํ๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
cancellable = urlSession
.dataTaskPublisher(for: url)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { print ("Received completion: \($0).") },
receiveValue: { print ("Received data: \($0.data).")})
receive(on: DispatchQueue.main)
์ผ๋ก,sink
์ ๋ก๊ทธ๋ฅผ Dispatch Queue์ main์์ ์ถ๋ ฅํฉ๋๋ค.
Share Data Task Publisher
๋คํธ์ํฌ ์์
์ ๋น์ฉ์ด ๋ง์ด ๋๋ ์์
์
๋๋ค. ๋ฐ๋ผ์ ํ๋์ ์์ฒญ์ผ๋ก ์ฑ์ ์ฌ๋ฌ ๋ถ๋ถ์์ ํ๋์ DataTaskPublisher
์ ๊ตฌ๋
ํ ์ ์์ต๋๋ค.
Combine์ share()
์ฐ์ฐ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ฐ์ฐ์ ๋ฐ subscriber์ ์ฐ๊ฒฐํ ์ ์์ผ๋ฉฐ, upstream publisher์ ํ๋์ downstream๋ง์ ํ์ธํ ์ ์์ต๋๋ค. ์ด๋ URLSession.DataTaskPublisher
์ด ๋คํธ์ํฌ ์์
์ ํ ๋ฒ๋ง ์ํํ๋ค๋ ์๋ฏธ์
๋๋ค.
let sharedPublisher = urlSession
.dataTaskPublisher(for: url)
.share()
cancellable1 = sharedPublisher
.tryMap() {
guard $0.data.count > 0 else { throw URLError(.zeroByteResource) }
return $0.data
}
.decode(type: User.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { print ("Received completion 1: \($0).") },
receiveValue: { print ("Received id: \($0.userID).")})
cancellable2 = sharedPublisher
.map() {
$0.response
}
.sink(receiveCompletion: { print ("Received completion 2: \($0).") },
receiveValue: { response in
if let httpResponse = response as? HTTPURLResponse {
print ("Received HTTP status: \(httpResponse.statusCode).")
} else {
print ("Response was not an HTTPURLResponse.")
}
}
)
-
share()
์ผ๋ก, ์ฌ๋ฌ subscriber์ด ์์ง๋ง ๋คํธ์ํฌ ์์ ์ ํ ๋ฒ๋ง ์ํํ ์ ์๋๋ก ํฉ๋๋ค. -
๋ ๊ฐ์ subscriber์
sharedPublisher
์ ์ฐ๊ฒฐํฉ๋๋ค. -
sharedPublisher
์ด ์ ๋ง๋ก ํ ๋ฒ๋ง ์์ฒญํ๋์ง ํ์ธํ๋ ค๋ฉด.print(_:to:)
๋ฅผ.share()
์ฐ์ฐ์ ์์ ๋ฃ์ด ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
URLSession์ downstream subscriber์ด ์ฐ๊ฒฐ๋์ง ์์๋, ์ฒซ ๋ฒ์งธ sink
๊ฐ ์ฐ๊ฒฐ๋๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ก๋ํฉ๋๋ค.
๋ค๋ฅธ subscriber์ ์ฐ๊ฒฐํ๋ ๋ฐ ์๊ฐ์ด ํ์ํ๋ค๋ฉด makeConnectable()
์ ์ฌ์ฉํ์ฌ Publisher.Share
publisher์ ConnectablePublisher
๋ก ๋ณํํฉ๋๋ค.
๊ทธ ํ ๋ชจ๋ subscriber์ด ์ค๋น๋๋ฉด, connect()
๋ฉ์๋๋ก ๋ฐ์ดํฐ ๋ก๋๋ฅผ ์์ํ ์ ์์ต๋๋ค.
Author And Source
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(wift ์ ๋ฆฌ - URLSession + Combine), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://velog.io/@yy0867/Swift-์ ๋ฆฌ-URLSession-Combine์ ์ ๊ท์: ์์์ ์ ๋ณด๊ฐ ์์์ URL์ ํฌํจ๋์ด ์์ผ๋ฉฐ ์ ์๊ถ์ ์์์ ์์ ์ ๋๋ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ
์ธ ๋ฐ๊ฒฌ์ ์ ๋
(Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค