-
Chapter 9: NetworkingRaywenderlich/Combine: Asynchronous Programming 2020. 8. 13. 01:35
Version
Swift 5.3, iOS 14, Xcode 12
프로그래머(programmers)로서 하는 일의 대부분 네트워킹(networking)을 중심(revolves)으로 이루어진다. 백엔드와 통신하고(communicating with a backend), 데이터를 가져오고(fetching data), 업데이트를 푸시하고(pushing updates), JSON을 인코딩 및 디코딩하는(encoding and decoding JSON) 등 이 모두가 모바일(mobile) 개발자(developer)의 일상적인 일이다.
Combine은 일반적인 작업을 선언적(declaratively)으로 수행하는 데 도움이 되는 몇 가지 선택(select) API를 제공한다. 이러한 API는 최신 애플리케이션(applications)의 두 가지 주요 구성 요소(components)를 중심(key)으로 한다:
URLSession
.- JSON encoding and decoding through the
Codable
protocol.
URLSession extensions
URLSession
은 네트워크(network) 데이터 전송(transfer) 작업을 수행하는데 권장되는 방법이다. 강력한 구성(configuration) 옵션과 완전히 투명한(transparent) 백그라운드(backgrounding) 지원(support)이 포함된 최신(modern) 비동기(asynchronous) API를 제공(offers)한다. 다음과 같은 다양한 작업(operations)을 지원한다:- URL의 내용을 검색(retrieve)하기 위한 데이터 전송(transfer) 작업.
- URL의 내용을 검색(retrieve)하여 파일에 저장(save)하는 다운로드(download) 작업.
- URL에 파일(files) 및 데이터를 업로드(upload)하는 작업.
- 두 당사자(parties) 간의 데이터 스트리밍(stream) 작업.
- 웹소켓(websockets) 연결(connect) 작업.
이 중에서 첫 번째 데이터 전송(transfer) 작업만 Combine publisher를 노출한다. Combine은
URLRequest
또는URL
만 사용하는 두 가지의(variants) 단일(single) API를 사용하여 이러한 작업을 처리(handles)한다.이 API를 사용하는 방법은 다음과 같다:
guard let url = URL(string: "https://mysite.com/mydata.json") else { return } let subscription = URLSession.shared //결과 구독을 유지하는 것이 중요하다. 그렇지 않으면 즉시 취소되고 요청이 실행되지 않는다. .dataTaskPublisher(for: url) //URL을 매개변수로 사용하는 dataTaskPublisher(for:)의 overload를 사용한다. .sink(receiveCompletion: { completion in if case .failure(let err) = completion { //항상 오류 처리를 해야 한다. 네트워크 연결은 실패하기 쉽다. print("Retrieving data failed with error \(err)") } }, receiveValue: { data, response in //결과는 Data와 URLResponse 객체의 튜플이다. print("Retrieved data of size \(data.count), response = \(response)") })
보다시피 Combine은
URLSession.dataTask
의 위(top)에서 투명한(transparent) 베어 본(bare-bones) publisher 추상화(abstraction)를 제공하고, 클로저(closure) 대신 publisher만 노출한다.Codable support
Codable
프로토콜(protocol)은 반드시 알아야 할 현대적(modern)이고 강력한(powerful) Swift 전용 인코딩(encoding) 및 디코딩(decoding) 메커니즘(mechanism)이다. 그렇지 않다면, raywenderlich.com의 Apple의 문서(documentation)와 튜토리얼(tutorials)에서 이에 대해 배울 수 있다.Foundation은
JSONEncoder
와JSONDecoder
으로 JSON의 인코딩(encoding)과 디코딩(decoding)을 지원한다.PropertyListEncoder
와PropertyListDecoder
를 사용할 수도 있지만, 네트워크(network) 요청(requests) 컨텍스트(context)에서는 유용하지 않다.이전 예제에서는 일부 JSON을 다운로드했다. 물론
JSONDecoder
로 이를 디코딩(decode)할 수 있다:let subscription = URLSession.shared .dataTaskPublisher(for: url) .tryMap { data, _ in try JSONDecoder().decode(MyType.self, from: data) } .sink(receiveCompletion: { completion in if case .failure(let err) = completion { print("Retrieving data failed with error \(err)") } }, receiveValue: { object in print("Retrieved object \(object)") })
위와 같이
tryMap
으로 JSON을 디코딩(decode)할 수 있지만, Combine은 도움이 되는 다른 연산자(operator)인decode(type:decoder:)
를 제공(provides)한다.위의 예제에서
tryMap
연산자(operator)를 다음으로 변경한다:.map(\.data) //dataTaskPublisher는 튜플을 내보내므로, map을 사용하여 data 부분만 따로 가져온다. .decode(type: MyType.self, decoder: JSONDecoder())
안타깝게도
dataTaskPublisher(for:)
는 튜플(tuple)을 내보내(emits)므로, 결과의Data
부분만 내보내(emits)는map(_:)
을 먼저 사용하지 않고는decode(type:decoder:)
를 직접(directly) 사용할 수 없다.이것의 장점은 publisher를 설정할 때
tryMap(_:)
클로저(closure)에서는JSONDecoder
를 매번 생성하는 것에 비해,decode(type:decoder:)
는JSONDecoder
를 한 번만(only once) 인스턴스화(instantiate)한다는 것이다.Publishing network data to multiple subscribers
publisher를 구독(subscribe)할 때마다, 작업이 시작(starts)된다. 네트워크(network) 요청(requests)의 경우에는, 복수(multiple)의 subscribers가 그 결과를 필요로 할 때 동일한 요청(request)을 여러 번 보내는(sending) 것을 의미한다.
놀랍게도 Combine에는 다른 프레임 워크(frameworks)처럼 이를 쉽게 구현하는 연산자(operators)가 없다.
share()
연산자(operator)를 사용할 수도 있지만, 결과가 나오기 전에 모든 subscribers가 구독(subscribe)해야하므로 까다롭다(tricky).캐싱(caching) 메커니즘(mechanism)을 사용하는 것 외에 한 가지 다른 해결 방법(solution)은
multicast()
연산자(operator)를 사용하여Subject
로 값(values)을 publishes하는ConnectablePublisher
를 만드는 것이다. subject를 여러 번(multiple times) 구독(subscribe)한 다음, 준비가 되면 publisher의connect()
메서드(method)를 호출(call)할 수 있다:let url = URL(string: "https://www.raywenderlich.com")! let publisher = URLSession.shared .dataTaskPublisher(for: url) //DataTaskPublisher를 생성 .map(\.data) //해당 데이터 매핑 .multicast { PassthroughSubject<Data, URLError>() } //multicast의 클로저는 적절한 유형의 subject를 반환해야 한다. //혹은 multicast(subject:)에 subject를 전달해야 한다. let subscription1 = publisher //publisher를 구독한다. //ConnectablePublisher이므로, 바로 작동하지 않는다. .sink(receiveCompletion: { completion in if case .failure(let err) = completion { print("Sink1 Retrieving data failed with error \(err)") } }, receiveValue: { object in print("Sink1 Retrieved object \(object)") }) let subscription2 = publisher //publisher를 구독한다. //ConnectablePublisher이므로, 바로 작동하지 않는다. .sink(receiveCompletion: { completion in if case .failure(let err) = completion { print("Sink2 Retrieving data failed with error \(err)") } }, receiveValue: { object in print("Sink2 Retrieved object \(object)") }) let subscription = publisher.connect() //준비가 되면, publisher를 연결한다. 작업을 시작하고 모든 subscribers에게 값을 보낸다.
multicast의 클로저(closure)는 적절한 유형의 subject를 반환하거나 기존 subject를 multicast(subject:)에 전달해야 한다. 13장(chapter), "Resource Management"에서 multicast에 대해 자세히 알아본다.
이 코드를 사용하여 요청(request)을 한 번 보내고, 결과를 두 구독자(subscribers)에게 공유(share)한다.
Note: 모든
Cancellable
s 항목을 저장(store)한다. 그렇지 않으면 현재 코드 범위(scope)를 벗어날 때 할당이 취소되며(deallocated), 이 경우에는 즉시(immediate) 할당이 취소(canceled)된다.Combine은 다른 리액티브(reactive) 프레임 워크(frameworks)와 달리, 이러한 종류의 시나리오(scenario)에 대한 연산자(operators)를 제공하지 않기 때문에 이 과정(process)은 약간 복잡(convoluted)하다. 18장(chapter), "Custom Publishers & Handling Backpressure"에서 더 나은 해결 방법(solution)을 살펴본다.
Key points
- Combine은
dataTaskPublisher(for:)
라는dataTask(with:completionHandler:)
메서드(method)에 대한 publisher-based 추상화(abstraction)를 제공한다. Data
값(values)을 내보내는(emits) publisher에서 내장된(built-in)decode
연산자(operator)를 사용하여Codable
준수(Codable-conforming) 모델(models)을 디코딩(decode)할 수 있다.- 여러(multiple) subscribers와 구독(subscription) 재생(replay)을 공유(share)하는 연산자(operator)는 없지만,
ConnectablePublisher
와multicast
연산자(operator)를 사용하여 이 동작(behavior)을 만들 수 있다.
'Raywenderlich > Combine: Asynchronous Programming' 카테고리의 다른 글
Chapter 11: Timers (0) 2020.08.13 Chapter 10: Debugging (0) 2020.08.13 Chapter 8: In Practice: Project "Collage" (0) 2020.08.11 Chapter 7: Sequence Operators (0) 2020.08.09 Chapter 6: Time Manipulation Operators (0) 2020.07.27