티스토리 뷰

사실 이전 번역은 불필요한 농담이나 비유는 번역을 안하고 넘어갔지만 이번에는 웬만하면 다 번역을 해보려고 노력했다. 한글로 된 책을 읽을 때는 미쳐 몰랐는데 개발자들의 이 말장난들을 어쩜 그렇게 재치있게 번역했는지, 전공 서적을 번역하는 분들 정말 존경스럽다.

원본 링크

 

 

 

이제 observable과 subject가 어떻게 다르게 작동하며, playground에서 어떻게 만들어서 실험하는지 알게 되었다.

하지만 매일매일 observable을 사용해서 UI를 데이터 모델과 연결하거나 데이터 모델로부터 새로운 controller를 제공하는 코드를 작성하는 것은 어려울 수 있다.

새롭게 배운 기술을 실제 세계에 적용시키는 것은 쉽지 않다. 챕터2 observable, 챕터3 subject에서 이론적인 부분을 다루었으니 이번 챕터는 실제로 적용되는 코드를 작성하면서 체화시켜보자.

"...in practice"에서는 실제로 애플리케이션을 완성시켜 볼 것이다. 시작 프로젝트는 Rx 코드를 포함하지 않는다. 추후에 추가되는 기능을 RxSwift framework로 작성할 것이며, 필요한 기능이 있을 때 마다 새로운 reactive 기술에 대해 배우게 될 것이다.

그렇다고 해서 새로 배우는 내용이 적다는 뜻은 아니다.

이 챕터에서는 사용자가 사진 모음을 만들 수 있도록 하는 애플리케이션을 observable을 사용해서 reactive 하게 만들어볼 것이다.

Getting started

시작 프로젝트 Combinestagram을 연다. 발음하기 힘들어서 마켓이 올리기 좋은 이름은 아닌 것같다. 하지만 곧 익숙해질 것이다.

필요한 pods를 모두 설치하고 Combinestagram.xcworkspace를 연다. 자세한 방법이 기억나지 않는다면 챕터1 Hello RxSwift를 참고하자.

Asset/Main.storyboard를 선택하면 인터페이스를 만들 수 있는 GUI를 볼 수 있다.

첫 화면에서 사용자는 현재 사진 모음과 현재 목록을 지울 수 있는 clear 버튼, 현재 목록을 저장할 수 있는 finished 버튼을 볼 수 있다. 그리고 우측 상단에 있는 +버튼을 누르면 카메라 롤에 있는 사진을 볼 수 있는 새로운 view controller를 볼 수 있다. 사용자는 사진 썸네일을 탭 해서 사진 모음에 사진을 추가할 수 있다.

view controller와 storyboard는 이미 연결되어 있고, UIImage+Collage.swift를 열어서 실제로 어떻게 추가되었는지 볼 수 있다.

이 챕터에서는 새로운 기술을 추가하는 방법에 집중할 것이다. 이제 시작할 시간이다!

 

 

 

Using a subject/relay in a view controller

controller에 선택된 사진을 값으로 가지는 BehaviorRelay<[UIImage]>를 추가하는 것 부터 시작해보자. 챕터3에서 배웠듯이 BehaviorRelay는 기존 변수와 비슷하게 사용할 수 있다.(언제든지 저장하고 있는 값을 변경할 수 있다.) 이 간단한 코드부터 시작해서 점차 subject와 custom observable로 발전해 나갈 것이다.

MainViewController.swift를 열고 다음 코드를 body에 추가한다.

private let bag = DisposeBag()
private let images = BehaviorRelay<[UIImage]>(value: [])

위 두 프로퍼티는 외부에서 사용할 일이 없기 때문에 private로 선언한다. 와! 캡슐화!

dispose bag의 소유자는 view controller이다. 따라서 view controller가 해제될 때 모든 observable의 구독이 해제(dispose)된다.

이건 Rx 구독의 메모리 관리를 편리하게 해 준다. 단지 구독을 dispose bag에 등록하는 것으로 view controller와 함께 메모리에서 해제되는 구독을 만들 수 있다.

하지만 안타깝게도 위 코드는 그렇게 동작하지 않는다. MainViewController는 root view controller이기 때문에 애플리케이션이 종료할 때 까지 살아있기 때문이다. view controller와 함께 해제되는 dispose bag의 실제 동작은 이 챕터 뒷부분에서 만들 다른 view controller에서 볼 수 있을 것이다.

처음에는 항상 같은 사진으로 사진모음을 만든다. 걱정하지 마라 Asset에 포함된 사진들은 바르셀로나 교외에서 찍은 멋진 사진들이다. 일단은 +를 누를 때 마다 항상 같은 사진이 추가되도록 만들 것이다.

actionAdd()를 찾아서 다음 코드를 추가하자.

let newImages = images.value + [UIImage(named: "IMG_1907.jpg")!]
images.accept(newImages)

첫째로, images가 방출하는 가장 최근 값에 새로운 UIImage를 추가한다. UIImage를 강제로 unwrap하는 것은 일단 무시하자. 이 챕터에서는 가능한 한 쉽게 중요 기능에만 집중할 수 있도록 하자.

그 다음, relay의 accept(_:)를 사용해서 images를 구독하고 있는 다른 구독자에게 최근 값을 방출할 수 있도록 했다.

images relay의 초기값은 비어있는 배열이었고, 사용자가 +버튼을 누를 때 마다 relay는 next 이벤트로 새로운 이미지가 추가된 배열을 방출한다.

사용자의 선택을 초기화 하기 위해서 actionClear()로 이동해서 다음 코드를 추가한다.

images.accept([])

짧은 코드로 사용자의 입력을 깔끔하게 처리했다. 이제 images를 관찰(observe)하면서 결과를 화면에 표시할 준비가 끝났다.

 

 

 

Adding photos to the callage

이제 연결된 images가 있으니, 사진 모음 프리뷰를 업데이트 할 수 있게 되었다.

viewDidLoad()에 images의 구독을 추가하자. images relay이지만, ObservableType이기 때문에 observable처럼 직접 구독할 수 있다.

images
	.subscribe(onNext: { [weak imagePreview] photos in
		guard let preview = imagePreview else { return }
		preview.image = photos.collage(size: preview.frame.size)
	})
	.disposed(by: bag)

images의 next 이벤트를 구독했다. 매 이벤트 마다 collage(images:size:)라는 helper로 collage를 생성한다. 마지막으로 view controller의 dispose bag에 등록한다.

이 챕터에서는 viewDidLoad()에서 observable을 구독한다. 이 책의 후반부에는 점차 다른 클래스에서도 구독하는 예제를 살펴볼 것이고 마지막에는 MVVM 구조로 만들어볼 것이다. 이제 사용자의 입력에 따라 images를 업데이트 하고 UI도 변경했다.

실제로 실행해서 잘 동작하는지 보자. +버튼을 네번 눌러서 사진을 4개 추가하면 이런 모습이 될 것이다.

정말 쉽군!

완성된 모습이 별볼일 없어 보일 수 있다. 하지만 조만간 실제 카메라롤에서 사진을 선택할 수 있는 기능을 추가할 것이다.

 

 

 

Driving a complex view controller UI

지금까지 결과를 실행해 보면 UI를 좀 더 스마트하게 개선할 수 있는 점들을 알아챌 수 있을 것이다.

  • 현재 선택된 사진이 하나도 없다면 clear 버튼을 비활성화 시킬 수 있다. (유저가 clear를 시킨 직후에도)
  • 마찬가지로 save 버튼 또한 항상 활성화 되어있을 필요는 없다.
  • 현재 선택된 사진의 개수가 홀수라면 save버튼을 비활성화 시킬 수도 있다. (사진이 홀수라면 완성된 사진 모음에 빈칸이 생길 것이다.)
  • 너무 많은 사진으로 만든 모음은 결과가 이상하게 나오기 때문에 선택할 수 있는 최대 사진을 6장으로 제한할 수도 있다.
  • 마지막으로 현재 선택한 사진의 제목을 화면에 표시해줄 수도 있다.

위 목록을 다시한번 천천히 읽어보면 reactive가 아닌 방법으로 위 내용을 구현하는게 여간 어려운게 아니라는 것을 느낄 수 있을 것이다.

하지만 RxSwift에서는 간단한 코드로 images를 구독하고 최신 상태를 업데이트 받을 수 있다.

viewDidLoad()에 다음 코드를 추가한다.

images
	.subscribe(onNext: { [weak self] photos in
		self?.updateUI(photos: photos)
	})
	.disposed(by: bag)

선택된 사진이 변경될 때 마다 updateUI(photos:)를 호출하도록 했다. updateUI라는 함수가 없기 때문에 클래스의 아무곳에 추가한다.

private func updateUI(photos: [UIImage]) {
	buttonSave.isEnabled = photos.count > 0 && photos.count % 2 == 0
	buttonClear.isEnabled = photos.count > 0
	itemAdd.isEnabled = photos.count < 6
	title = photos.count > 0 ? "\(photos.count) photos" : "Collage"
}

위에서 생각했던 개선에 맞게 UI를 업데이트하는 코드를 추가했다. 모든 코드는 한 자리에 있고 가독성도 좋다. 다시 애플리케이션을 실행하고 모든것이 제대로 동작하는지 테스트 해 보자.

이제 Rx를 iOS에 적용했을 때 얼마나 이점이 많은지 보일 것이다. 지금까지 작성한 코드를 보자. 얼마나 짧고 간결한가!

 

 

 

Talking to other view controllers via subjects

이번 섹션에서는 사용자가 카메라롤에서 임의로 선택한 사진을 main view controller와 연동할 수 있도록 하는 PhotosViewController를 만들어볼 것이다. 결과물은 아마 이전보다 더 흥미로울 것이다.

첫째로, navigation stack에 PhotosViewController를 추가해야 한다. MainViewController.swift를 열고 actionAdd()함수를 찾아서 다음 코드의 주석을 해제한다.

let photosViewController = storyboard!.instantiateViewController(withIdentifier: "PhotosViewController") as! PhotosViewController

navigationController!.pushViewController(photosViewController, animated: true)

storyboard에서 만든 PhotosViewController의 인스턴스를 생성하고 navigation stack에 추가했다. 실행해서 +버튼을 눌러 카메라롤이 잘 나오는지 보자.

최초로 실행했을 때는 카메라롤에 대한 접근을 허용하도록 하는 경고 팝업을 보게 될 것이다.

허용(또는 OK)를 선택하면 photos controller가 어떻게 생겼는지 볼 수 있다. 실제로 실행화면은 실행하고 있는 환경에 따라 다를 수 있고, 앱을 종료하고 다시 실행해야 할 수도 있다.

이번에는 iPhone 시뮬레이터에 있는 샘플 사진들을 볼 수 있을 것이다.

기존 cocoa 패턴으로 개발한다면 다음 작업은 photos view controller가 main view controller에게 이벤트를 전달할 수 있도록 delegate를 만드는 것이다.(이건 reactive 방식이 아니다)

하지만 RxSwift에는 어떤 두 클래스라도 연결할 수 방법이 있다. Observable! observable은 어떠한 메세지도 전달할 수 있기 때문에 따로 protocol을 정의할 필요가 없다.

 

 

 

Creating an observable out of the selected photos

이제 PhotosViewController에, 사용자가 카메라롤에서 사진을 선택할 때 마다 next 이벤트를 방출하는 subject를 추가할 것이다. PhotosViewController.swift를 열고 맨 위에 다음 코드를 추가하자.

import RxSwift

PublishSubject는 선택된 사진을 방출할 수 있어야 하지만, 외부에서 접근해서 onNext(_:)를 호출을 하는 것은 원하지 않을 것이다.

PhotosViewController에 다음 프로퍼티를 추가한다.

private let selectedPhotosSubject = PublishSubject<UIImage>()
var selectedPhotos: Observable<UIImage> {
	return selectedPhotosSubject.asObservable()
}

선택된 사진을 방출할 수 있는 PublishSubject와 이를 observable로 접근할 수 있는 selectedPhotos라는 public 접근자를 생성했다.

이 프로퍼티를 구독하면 sequence를 방해하지 않고 선택된 사진을 관찰할 수 있다.

PhotosViewController에는 이미 카메라롤에 있는 사진들을 읽고 collection view로 표현하는 코드가 포함되어 있다. 해야할 일은 사용자가 collection view에서 선택한 사진을 방출하는 코드를 작성하는 일 뿐이다.

스크롤을 내려서 collectionView(_:didSelectItemAt:)으로 이동한다. 안에 있는 코드는 선택된 사진을 약간 하이라이트 시켜서 사용자에게 시각적인 정보를 제공한다.

이어서, imageManager.requestImage(...)는 completion closure에서 선택된 사진에 대한 정보를 가진 image, info 파라미터를 제공한다. 이 closure에서 next 이벤트를 방출할 것이다.

closure의 guard 아래에 다음 코드를 추가한다.

if let isThumbnail = ifno[PHImageResultIsDegradedKey as NSString] as? Bool, !ISThumbnail {
	self?.selectedPhotosSubject.onNext(image)
}

선택된 이미지가 썸네일인지 원본인지 확인하기 위해 info dictionary를 사용했다. imageManager.requestImage(...)는 각 버전에 대해 모두 closure를 실행한다. 원본일 때 onNext(_:)를 호출해서 원본 이미지를 방출하게 한다.

이게 RxSwift가 view controller에서 다른 view controller로 observable sequence를 방출하는 방법이다. delegate 프로토콜이나 다른 헛짓거리는 필요 없다.

보너스로 프로토콜을 제거함으로써 controller들 간의 관계가 굉장히 간결해졌다.

 

 

 

Observing the sequence of selected photos

다음은 MainViewController.swift로 돌아와서 선택된 사진 sequence를 구독하는 작업을 마무리 할 차례이다.

actionAdd()를 찾아서 navigation stack에 PhotosViewController를 추가하는 부분 위에 다음 코드를 작성하자.

photosViewController.selectedPhotos
	.subscribe(
		onNext: { [weak self] newImage in

		},
		onDisposed: {
			print("Completed photo selection")
		}
	)
	.disposed(by: bag)

controller를 stack에 추가하기 전에 selectedPhotos observable을 구독하도록 했다. 특이한 점은 onNext와 함께 onDisposed 이벤트도 처리하도록 구독했다는 점인데 왜 그런지는 잠시 후에 알게된다.

onNext closure에 처음에 작성했던 것과 같은 다음 코드를 추가한다. 단, 이번에는 정해진 사진이 아니라 카메라롤에서 선택한 사진이 추가될 것이다.

guard let images = self?.images else { return }
images.accept(images.value + [newImage])

애플리케이션을 실행하고 카메라롤에서 몇개의 사진을 선택한 후 되돌아와서 결과를 보자. 멋져!

 

 

 

Disposing subscriptions - review

모든 코드가 정상적으로 동작하는 것 처럼 보인다. 하지만 사진을 몇개 추가하고 메인 화면으로 돌아온 후 콘솔을 보자.

"Completed photo selection"이라는 문구가 보이는가? 아까 onDisposed에서 출력하도록 print를 작성했지만 출력되지 않는다! 그말은 구독이 취소되지 않았다는 뜻이고 아직 메모리를 잡아먹고 있다는 뜻이다!

왜지? 구독을 하는 코드를 다시 보자. main view controller의 dispose bag에 구독을 등록했다. 이전 챕터에서 설명했던 대로 구독은 sequence가 error나 completed로 종료되거나 dispose bag이 해제될 때 같이 해제된다고 했다.

bag을 해제하기 위해 main view controller를 해제하거나 photos sequence를 종료하지 않았기 때문에 구독이 종료되지 않는다.

controller가 화면에서 사라질때(disappear) completed 이벤트를 방출하도록 할 수 있다. 이 방법은 모든 구독자에게 completed 이벤트를 방출해서 자동으로 해제할 수 있도록 도와준다.

PhotosViewController.swift를 열고 viewWillDisappear(_:)를 찾아서 onCompleted() 이벤트를 추가한다.

selectedPhotosSubject.onCompleted()

완벽해! 이제 이 챕터의 마지막 단계만 남았다. 구닥다리 옛 함수들을 겁나 쩌는 reactive 함수로 바꿔보자.

 

 

 

Creating a custom observable

지금까지 BehaviorRelay, PublishSubject, 그리고 Observable을 다뤄보았다. 준비운동으로 커스텀 Observable 클래스를 만들고 기존 API 함수들을 reactive로 변환해보자. 사진 모음을 저장하기 위해서 Photos framework를 사용할 것이다.

PHPhotoLibrary를 확장해서 reactive 기능을 추가할 수도 있지만 쉽게 가자. 이 챕터에서는 PhotoWriter라는 새로운 클래스를 만들 것이다.

사진을 저장하기 위한 observable을 만드는 것은 쉬울 것이다. 사진이 성공적으로 저장되었다면 asset ID와 함께 completed 이벤트를 방출할 수있고, 실패하면 error 이벤트를 방출할 수 있다.

 

 

 

Wrapping an exiting API

Classes/PhotoWriter.swift를 열고(이 파일에는 빨리 시작하기 위해 몇가지 코드가 작성되어 있다.

첫째로, import를 추가한다.

import RxSwift

그리고 사진을 저장할 observable을 생성할 수 있는 static 함수를 추가한다.

static func save(_ image: UIImage) -> Observable<String> {
	return Observable.create { observer in
	
	}
}

save(_:)는 Observable<String>을 생성해서 반환한다. 사진을 저장하고 나서는 각 사진에 대한 고유 정보를 방출할 것이기 때문이다.

Observable.create()는 새로운 observable을 생성하고, 이제 이 closure에 나머지 내용을 추가해야 한다.

var savedAssetId: String?
PHPhotoLibrary.shared().performChanges({

}, completionHandler: { success, error in

})

performChanges(_:completionHandler)의 첫번째 closure에서 photo asset을 생성하고 두번째 closure에서 그 asset의 ID 또는 error 이벤트를 방출할 것이다.

첫번째 closure에 다음 코드를 추가한다.

let request = PHAssetChangeRequest.creationRequestForAsset(for: image)
savedAssetId = request.placeholderForCreatedAsset?.localIdentifier

PHAssetChangeRequest.creationRequestForAsset(from:)으로 새로운 photo asset을 만들었고, 그 ID를 savedAssetId에 저장했다. 두번째 closure에 다음을 추가하자.

DispatchQueue.main.async {
	if success, let id = savedAssetId {
		observer.onNext(id)
		observer.onCompleted()
	} else {
		observer.onError(error ?? Errors.couldNotSavePhoto)
	}
}

성공적으로 savedAssetId에 유효한 ID를 응답 받으면 next 이벤트를 방출하고 completed 이벤트로 종료한다. 응답이 잘못 되었다면 error을 방출한다.

이것으로 observable sequence 로직이 완성되었다.

Xcode는 아마 create가 disposable을 반환하지 않았다는 경고를 줄 것이다. 따라서 create의 마지막줄에 Disposable을 반환하는 다음 줄을 추가한다.

return Disposables.create()

완성된 save() 함수는 아래와 같다.

static func save(_ image: UIImage) -> Observable<String> {
	return Observable.create({ observer in
		var savedAssetId: String?
		PHPhotoLibrary.shared().performChanges({
			let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
			savedAssetId = request.placeholderForCreatedAsset?.localIdentifier
		}, completionHandler: { success, error in
			DispatchQueue.main.async {
				if success, let id = savedAssetId {
					observer.onNext(id)
					observer.onCompleted()
				} else {
					observer.onError(error ?? Errors.couldNotSavePhoto)
				}
			}
		})
		return Disposables.create()
	})
}

단 한번의 next 이벤트를 방출하기 위해서 굳이 observable을 써야하나 의문이 생길 수 있다.

이전 챕터에서 어떤 것들을 배웠는지 다시 되새겨보자. 아래 목록중 하나로 observable을 만들 수 있다고 배웠다.

  • Observable.never() : 어떤 값도 방출하지 않고 종료되지 않는 observable sequence를 만든다.
  • Observable.just(_:) : 하나의 값을 방출하고 completed로 종료된다.
  • Observable.empty() : 아무 값도 방출하지 않고 completed로 종료된다.
  • Observable.error(_) : 아무 값도 방출하지 않고 error로 종료된다.

목록에서 볼 수 있듯이, observable은 0개 이상의 next 이벤트를 방출한 후 completed나 error 이벤트를 방출하고 종료한다.

PhotoWrite의 경우 save는 한번만 일어나기 때문에 하나의 next 이벤트를 발생시킨다. 그래서 next+completed 또는 error를 방출해야 하는데 위 목록에는 그런 조합이 없어보인다.

여기서 "그럼 Single은?"이라고 외치면 보너스 점수가 있다. 그럼 Single은?

 

 

 

RxSwift traits in practice

챕터2 "Observable"에서 RxSwift에는 특수한 경우에 쓸 수 있도록 Observable을 확장한 trait이라는 것이 있다고 배웠다.

이 챕터에서는 빠른 복습을 통해 Combinestagram에 실제로 trait을 적용시켜볼 것이다. Single부터 시작하자.

 

Single

챕터2에서 배웠듯이 Single은 Observable의 전문화된 케이스로, error 또는 success(Value)라는 유일한 이벤트를 방출한다. 속을 들여다 보면 success는 단지 next+completed 쌍이다.

이런 종류의 trait은 파일을 저장하거나 불러오는 작업, 파일을 다운로드 하는 작업 같이 기본적으로 비동기적으로 값을 산출해내는 작업에 유용하다. Single의 유스케이스는 다음 두개로 나눌 수 있다.

  1. 이 챕터의 PhotoWriter.save(_:)와 같이, 성공했을 때 하나의 값만 방출하는 연산이 필요한 경우.

    Observable을 생성하지 않고 Single을 직접 생성할 수 있다. 사실 이 챕터의 도전과제에서 PhotoWirter의 save(_) 함수를 Single로 구현해볼 것이다.

  2. sequence가 하나의 element만 방출하고, 2개 이상의 element를 방출했다는 것은 error라는 의도를 더욱 분명하게 만든다.

    그렇게 하기 위해서 observable을 구독할 때 .asSingle()을 사용해서 Single로 변환할 수 있다. 이 챕터를 다 읽고 직접 해보길 바란다.

 

Maybe

Maybe는 Single과 비슷하지만 정상적인 completed 이벤트에도 값을 방출하지 않을 수도 있다(maybe)는 점이 다르다.

위에서 만든 사진 예시에서 Maybe가 어떻게 사용될 수 있을지 상상해보자. 애플리케이션은 자기 자신만의 커스텀 앨범을 저장한다. UserDefaults에 저장된 앨범 식별자를 계속 유지해서, 앨범을 열려고 할 때 마다 그 ID를 쓰려고 할 것이다. 이제 다음 상황들을 처리하기 위해 open(albumId:) -> Maybe<String> 같은 함수를 디자인할 수 있다.

  • 전달된 albumID를 가진 앨범이 아직 존재할 경우, completed 이벤트를 방출한다.
  • 사용자가 그동안 앨범을 지웠다면, 새로운 앨범을 만들고 새로운 ID를 next 이벤트로 방출해서 UserDefaults에서 사용할 수 있도록 한다.
  • 무언가 잘못되어서 더이상 사진 앨범에 접근할 수 없게 된다면 error 이벤트를 방출한다.

다른 trait과 동일하게 "기본" Observable로도 위 목적을 충분히 달성할 수 있다. 하지만 Maybe를 사용하면 나중에 이 코드를 볼 다른 프로그래머들(미래의 본인도 포함)에게 더욱 명확한 의도를 알릴 수 있다.

Single과 마찬가지로 Maybe도 Maybe.create({...})로 생성할 수 있고, 아니면 그냥 아무 observerble sequence를 .asMaybe()로 변환할 수 있다.

 

Completable

마지막 trait은 Completable이다. 이 trait은 구독이 종료(dispose)하기 전에 단 하나의 completed 또는 error 이벤트를 방출한다.

completed나 error 이벤트만 방출할 수 있도록 모든 next 이벤트를 무시하도록 하는 ignoreElements() operator를 통해 observable sequence를 completable로 변환할 수 있다.

아니면 다른 trait을 생성할 때와 비슷한 방법으로 Completable.create { ... }을 사용할 수도 있다.

아무 값도 방출하지 못하고 단지 완료나 실패만 방출할 수 있는 Completable같은 sequence를 왜 사용하는지 의문이 들 수도 있다. 하지만 비동기 작업의 성공 여부만 알 수 있는 이 기능이 사용되는 유스케이스를 보면 그 개수에 놀라게 될 것이다.

Combinestagram으로 돌아가기 전에 한가지 예제를 보자. 사용자가 작업중인 와중에 앱이 도큐먼트를 자동저장한다고 가정해보자. 백그라운드에서 돌아가는 비동기 저장이 성공하면 작은 알림창을 띄우거나 실패했을 때 애러창을 띄울 수 있다.

saveDocument() -> Completable에 자동저장 로직이 구현되어 있다고 가정하자.

saveDocument()
  .andThen(Observable.from(createMessage))
  .subscribe(onNext: { message in
    message.display()
  }, onError: {e in
    alert(e.localizedDescription)
  })

andThen operator를 사용하면 성공 이벤트에 더 많은 completable이나 observable을 연결해서 최종 결과값을 구독할 수 있다. 위 코드의 경우 saveDocument()로 생성된 Completable이나 andThen으로 추가한 Observable중 아무나 error 이벤트를 방출해도 onError closure에서 처리된다.

앞으로 나올 두 챕터에서 Completable을 사용하게 될 것이다. 지금은 Combinestagram으로 돌아가자.

 

 

 

Subscribing to your custom observable

현재 기능(사진을 사진 라이브러리에 저장)은 trait의 유스케이스 중 하나이다. PhotoWriter.save(_)는 단 한번 방출하거나(새로운 asset ID) error를 방출하기 때문에 Single을 사용하기에 아주 적합하다.

이제 재미있는 일만 남았다. 임의의 Observable을 만들어서 기존의 고지식한 것들을 날려버리자!

MainViewController.swift를 열고 actionSave()를 찾아서 안에 Save 버튼을 위한 다음 코드를 작성한다.

guard let image = imagePreview.image else { return }

PhotoWriter.save(image)
  .asSingle()
  .subscribe(
    onSuccess: { [weak self] id in
      self?.showMessage("Saved with id: \(id)")
      self?.actionClear()
    },
    onError: { [weak self] error in
      self?.showMessage("Error", description: error.localizedDescription)
    }
  )
  .disposed(by: bag)

위에서 현재 사진모음을 저장하기 위해 PhotoWriter.save(image)를 호출했다. 그리고 반환된 Observable을 하나의 element만 방출하는 Single로 변환해서 성공, 실패 결과를 화면에 출력하도록 했다. 추가로 작업이 성공적으로 끝나면 현재 목록을 초기화 시켰다.

Note: asSingle()은 sequence가 2개 이상의 element를 방출하려고 하면 error로 처리하도록 보장한다.

마지막으로. 완성된 애플리케이션을 실행해서 멋진 사진 모음을 만들어 저장해보자.

저장된 결과는 사진앱에서 확인할 수 있다는 것을 잊지말자!

이렇게 Section 1을 마무리 했다.(축하합니다!)

이제 RxSwift 세계의 파다완 단계는 지났지만 아직 제다이가 되기는 멀었다. 너무 느리다고 다크 사이드에 빠지지는 말아라. 아직 네트워크, 스레드, 애러 처리 등과의 전투가 남아있다.

그러기 전에 RxSwift의 가장 강력한 면을 훈련해야 한다. Section 2 "Operators and Best Practices"에서 다룰 operator는 Observable에게 슈퍼파워를 부여하고 신세계를 맞보여 줄 것이다.

'Programming > RxSwift' 카테고리의 다른 글

RxSwift Beginner | Subject(번역)  (0) 2021.01.25
RxSwift Beginner | Observable(번역)  (0) 2021.01.24
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함