티스토리 뷰

맥북을 중고로 판매하고 새로운 구매해서 기다리고 있는 관계로 직접 코딩을 할 수 없게 되었다. 배송을 기다리면서 RxSwift 문서를 번역해보기로 했다. 번역은 처음이라 문장이 너무 어색한데 하다보면 점점 괜찮아지지 않을까?

원문 링크

 

 

 

앞서 RxSwift에 대해 기본적인 컨셉을 배웠다면, 이제는 observable을 배울 차례다.

이 챕터에서 observable을 생성(create)하고 구독(subscribe)하는 몇가지 예제를 다룰 것이다. 일부 사용법은 실세계에서 모호하게 느껴질 수 있지만 observable을 다루는 중요한 스킬과 수많은 활용성을 배울 수 있을 것이다. 이 기술을 앞으로 사용하게 될 것이다.

 

 

 

Getting started

이 챕터에서는 RxSwift framework와 playgound가 포함된 Xcode 프로젝트를 사용할 것이다. 터미널을 열고 프로젝트 폴더의 RxPlaygound 프로젝트 폴더로 이동한다. 끝으로, bootstrap.sh 스크립트를 실행한다.

./bootstrap.sh

실행하는데 시간이 조금 걸릴 수 있다. 앞으로 이 playground 프로젝트를 열 때 마다 위 과정을 반복해야 한다는 것을 잊지말자.

Project navigator에서 RxSwiftPlayground 프로젝트를 선택하면 아래와 같은 화면을 볼 수 있다.

Projcet navigator에서 Sources를 통해 playground 페이지를 열고 SupportCode.swift를 선택한다. 파일은 간단한 테스트 closure를 실행할 수 있는 다음 함수가 작성되어 있다.

public func example(of description: String, action: () -> Void) {
	print("\n--- Example of:", description, "---")
	action()
}

앞으로 나오게 될 예제들을 이 함수를 사용해서 캡슐화 할 것이다. 이 함수를 약식으로 사용하는 방법도 배울 것이다.

하지만 그 전에, 짚고 넘어가야 할 문제가 있다: observable이 대체 뭐야?

 

 

 

What is an observable?

Observable은 Rx의 심장과도 같다. 이 챕터에서 observable이 무엇인지, 어떻게 생성하는지, 그리고 어떻게 사용하는지에 대해 공부하게 될 것이다.

Rx에서 "observalbe", "observable sequence", "sequence"와 같은 용어들을 볼 수 있는데 이것들은 모두 같은 의미이다. 종종 "stream"이라는 단어가 다른 reactive programming 환경으로부터 넘어와서 사용되는 것을 볼 수도 있는데 "stream"도 같은 의미이다. 하지만 RxSwift에서는 이 모두를 sequence라고 부른다.

observable은 특별한 능력을 가진 sequence다. 그 중 하나가 바로(사실 가장 중요한) **비동기성(asynchronous)**이다. observable은 **방출(emitting)**이라고 하는 이벤트를 일정 기간동안 생성한다. 이벤트는 값, custom type의 instance 또는 tap같은 gesture recognizer를 포함할 수 있다.

이걸 설명하는 가장 좋은 방법 중 하나는 시간의 흐름에 따라 값을 단순히 값을 기록한 marble diagram을 이용하는 것이다.

왼쪽에서 오른쪽으로 향하는 화살표는 시간을 나타낸다. 숫자가 쓰여진 원은 이 sequence의 값(element)를 뜻한다. 시간의 흐름에 따라 1, 2, 3이 순서대로 방출된다. 얼만큼의 시간간격 마다 값이 방출되는지 궁금할 수 있다. 답은 observable의 생명주기 안에서 언제든지 발생할 수 있다. 이제 자연스럽게 다음 장에서 observable의 생명주기에 대해 배울 수 있다.

 

 

 

Lifecycle of an observable

이전에 본 marble diagram에서 observable은 3개의 값을 방출했다. observable이 값을 방출할 때 next라는 이벤트를 호출한다.

이번에는 observable의 끝을 뜻하는 세로줄이 있는 diagram을 보자.

이 observable은 세번의 tap 이벤트를 방출하고 끝난다. 끝은 completed 이벤트를 호출하고 이는 **종료(terminated)**를 뜻한다. 예를 들어, tap을 할 수 있는 view가 사라지는 경우가 있다. 중요한 점은 observable은 끝났고, 더이상 어떤 것도 방출할 수 없다는 점이다. 이 경우 정상적인 종료라고 할 수 있지만 어떤 경우에는 일이 잘못될 수 있다.

오류는 빨간 x로 표기한다. observable은 error와 함께 error 이벤트를 호출한다. observable이 error를 방출하면 completed로 종료되는 것과 동일하게 어떤 것도 방출할 수 없게 된다.

요약:

  • observable은 next이벤트로 값을 방출한다.
  • completederror이벤트로 종료될 때 까지 지속된다.
  • 한 번 종료된 observable은 더이상 어떤 것도 방출할 수 없다.

이벤트는 enum case로 표현한다. 실제 RxSwift의 이벤트 코드를 보자.

public enum Event<Element> {
	case next(Element)
	case error(Swift.Error)
	case completed
}

next이벤트는 Element를 전달할 수 있고, error는 Swift.Error를 전달할 수 있다. completed는 단순히 종료를 알리고, 아무 데이터도 전달하지 않는다.

이제 observable이 무엇인지 알아보았으니 실제로 몇가지 observable을 만들어서 어떻게 동작하는지 알아보자.

 

 

 

Creating observable

RxSwiftPlayground로 이동해서 아래 코드를 추가한다.

example(of: "just, of, from") {
	// 1
	let one = 1
	let two = 2
	let three = 3

	// 2
	let observable = Observable<Int>.just(one)
}
  1. 예제에서 사용할 Int를 정의한다.
  2. just를 사용해서 one이라는 상수를 가진 Int형 observable sequence를 생성한다.

just는 Observable의 static method로, 단지(just) 하나의 값을 가질 수 있는 observable sequence를 생성하기 때문에 아주 적합한 이름이다. 하지만 Rx에서 method는 operator라고 부른다. 다음은 of operator에 대해 알아볼 것이다.

같은 코드에 다음을 추가한다.

let observable2 = Observable.of(one, two, three) 

이번에는 자료형을 정의하지 않았다. Int를 여러개 전달했기 때문에 [Int]로 추론되었을 것이라고 생각할 수도 있다. 하지만 생성된 observable2를 Option-click으로 보면 배열이 아니라 단순 Int로 추론되는데, of operator는 가변적인 파라미터를 가질 수 있고, swift는 이를 기반으로 자료형을 추론하기 때문이다. 즉, Int를 여러개 전달한 것이지, [Int]를 전달한 것이 아니기 때문에 Int로 추론되는 것이다.

[Int]자료형으로 observable을 생성하고 싶다면 아래처럼 직접 배열을 전달해야 한다.

let observable3 = Observable.of([one, two, three])

이렇게 만들어진 observable3를 Option-click 해보면 [Int]로 추론된 것을 볼 수 있다. of 뿐 아니라 just operator도 배열을 받을 수 있다.

다음 알아볼 operator는 from이다. 다음 코드를 아래에 추가한다.

let observable4 = Observable.from([one, two, three])

from operator는 배열의 각 element에 대해 observable을 생성한다. 그래서 observable4를 Option-click 해보면 [Int]가 아니라 Int인 것을 볼 수 있다. from은 배열만 받을 수 있다.

아직 아무것도 출력되지 않는 이유는 아직 출력하는 코드를 입력하지도, 실행하지도 않았기 때문이다. subscribing으로 observable을 구독해보자.

 

 

 

Subscribing to observable

iOS 개발을 경험해 봤다면, NotificationCenter(observer들에게 알림을 전달한다.)에 익숙할 것이다. RxSwift의 Observable과 비슷한 것 처럼 보이지만 차이가 있다. 예를 들어, handler closure를 가진 UIKeyboardDidChangeFrame notification을 보자.

let observer = NotificationCenter.default.addObserver(
	forName: UIResponder.keyboardDidChangeFrameNotification,
	object: nil,
	queue: nil) { notification in 
		// handle receiving notification
	}

RxSwift의 구독도 비슷하다. addObserver() 대신 subscribe()를 사용하게 된다. .default 싱글톤을 사용하는 NotificationCenter와 달리, Rx에서 observable은 모두 개별적인 instance를 가진다.

더욱 중요한 점은, observable은 subscriber가 없으면 어떠한 이벤트도 호출하지 않고, 어떠한 작업도 하지 않는다는 점이다.

observable은 sequence의 일종이라는 것을 기억하자. 그리고 observable을 구독하는 것은 Swift standard library Iterator의 next()를 호출하는 것과 비슷하다.

let sequence = 0..<3

var iterator = sequence.makeIteraotr()

while let n = iterator.next() {
	print(n)
}

observable을 구독하는 것은 더욱 간소화 되어서 observable이 방출하는 모든 event type에 대해 handler를 추가할 수 있다. observable은 next, error, completed 이렇게 세가지 type의 이벤트를 방출할 수 있는데, next 이벤트는 handler에게 값을 방출하고, error 이벤트는 error instance를 방출한다.

이것들을 실제로 보기 위해서 예제를 작성해보자. 새로운 예제 코드를 추가하려면 반드시 이전 예제를 마무리하고 독립적으로 작성해야 한다는 것을 잊지말자. 예를들어 아래 예제는 example(of: "just, of, from")과 독립적으로 작성된 예제코드이다.

example(of: "subscribe") {
	let one = 1
	let two = 2
	let three = 3

	let observable = Observable.of(one, two, three)
}

of operator를 사용한다는 점을 빼면 이전 예제와 비슷한 코드이다. 이제 observable을 구독하기 위해 아래에 코드를 추가한다.

observable.subscribe {event in
	print(event)
}
Note: 어떤 output이 있을 때 콘솔이 활성화되어야 하지만, 메뉴에서 View → DebugArea → ActivateConsole을 선택하여 수동으로 활성화 할 수 있다. 콘솔은 print의 내용이 출력되는 곳이다.

subscribe를 Option-click 해보면 Event<Int> 타입을 받고, Void를 반환하는 closure를 파라미터로 받고, Disposable이라는 것을 반환하는 것을 볼 수 있다. disposable에 대해서는 조만간 배우게 될 것이다.

--- Example of: subscribe ---
next(1)
next(2)
next(3)
completed

observable은 각 element를 next로 방출한 후, completed 이벤트를 방출하고 종료된다. observable로 작업을 하다보면 결국 이벤트 자체 보다는 함께 방출되는 element을 더욱 중요시 하게 될 것이다.

방출되는 element를 직접 다루기 위해 subscribe 코드를 아래와 같이 수정할 수 있다.

observable.subscribe {event in
	if let element = event.element {
		print(element)
	}
}

Event는 element를 가지고 있는데, next 이벤트만이 element를 가지고 있기 때문에 optional이다. element를 unwrap하여 nil이 아닌 경우에만 사용하도록 만들 수 있다. 이제 event와 결합된 형태가 아니라 element를 직접적으로 출력할 수 있게 되었다.

1
2
3

이건 굉장히 자주 사용되는 패턴이다. RxSwift는 이를 좀 더 쉽게 접근할 수 있도록 observable이 방출하는 각 event에 대해 처리할 수 있는 subscribe operator를 제공한다.

observable.subscribe(onNext: {element in
	print(element)
})
Note: 만약 Xcode 자동완성 기능이 켜져 있다면 onNext를 타이핑 했을 때 다른 이벤트도 처리할 수 있는 코드를 볼게 될 수 있지만 지금은 무시해도 괜찮다.

이제 나머지 이벤트는 무시한 채 next 이벤트에 의해 방출되는 element만 관리할 수 있다. onNext closure는 next 이벤트에 의해 방출되는 element 값을 가지고 있기 때문에 전처럼 event로부터 element를 얻어올 필요가 없다.

이제 하나의 element를 가지는 observable 부터, 여러 element를 가지는 observable을 생성하는 방법을 알아보았다. 그렇다면 아무 element를 가지지 않는 observable도 존재할 수 있을까? empty operator는 어떤 element도 가지지 않고 completed 이벤트만 방출하는 observable을 생성한다.

example(of: "empty") {
	let observable = Observable<Void>.empty()
}

observable을 생성할 때 타입 추론이 모호하거나 안되는 상황이라면 반드시 타입을 지정해 주어야 한다. empty는 말 그대로 아무 값을 가지지 않는 observable을 생성하기 때문에 보통 Void 타입을 사용한다.

observable.subscribe(
	// 1
	onNext: {element in
		print(element)
	},
	// 2
	onCompleted: {
		print("completed")
	}
}
  1. next 이벤트를 처리하는 clousure
  2. completed 이벤트를 처리하는 closure. completed 메세지를 출력한다.

코드를 실행해 보면 다음과 같은 결과를 볼 수 있다.

--- Example of: empty ---
completed

그렇다면 empty observable은 언제 사용할 수 있을까? 즉시 종료되거나 값이 0인 observable이 필요할 때 사용할 수 있다.

empty operator와 반대로, 아무 element도 방출하지 않고 종료되지도 않는 observable을 생성하는 never operator도 있다. 무한한 수명을 가지는 observable이라고 볼 수 있다.

example(of: "never") {
	let observable = Observable<Void>.never()
	
	observable.subscribe(
		onNext: {element in
			print(element)
		},
		onCompleted: {
			print("Completed")
		})
}

실행해 보면 아무것도 출력되지 않는다. 심지어 Completed 메세지도 출력되지 않는다. 그럼 지금 코드가 정상적으로 동작한다는 것을 어떻게 알 수 있을까? 이 의문은 Challenge 챕터에서 풀릴 것이니 조금만 더 참고 기다려보자.

지금까지는 특정 element나 값에 대한 observable을 생성했지만 range value에 대해서도 observable을 생성할 수 있다.

example(of: "range") {
	// 1
	let observable = Observable<Int>.range(start: 1, count: 10)

	observable.subscribe(
		// 2
		onNext: {i in
			let n = Double(i)

			let fibonacci = Int(((pow(1.61803, n) - pow(0.61803, n)) / 2.23606).rounded()
			print(fibonacci)
		})
}
  1. range operator를 사용해서 1부터 10까지 Int 범위를 가지는 observable을 생성했다.
  2. 피보나치 항을 계산해서 출력한다.

사실 방출된 element를 변경(transform)해서 출력한다는 점에서 onNext보다 더 좋은 방법이 있지만 이 방법은 나중에 배울 챕터인 "Transforming Operator"에서 다루게 된다.

never() 예제를 제외한 나머지 예제를 다루면서 알 수 있는 사실은 모든 observable은 자동으로 completed 이벤트를 호출해서 종료된다는 점이다. 이러한 예제를 다루면서 osbervable을 생성하고 구독하는 기본적인 기법에 대해 배웠고, 이제 더 나아가기 전에 몇가지 관리 방법에 대해 배울 차례이다.

 

 

 

Disposing and terminating

observable은 구독이 있기 전 까지는 아무 작업도 하지 않는다는 점을 기억하자. subscribe는 observable이 어떤 작업을 수행할 수 있도록 하는 트리거와 같고, error나 completed로 종료되기 전까지 계속 새로운 이벤트를 호출하도록 만든다. 하지만 임의로 구독을 취소하고 observable을 종료할 수도 있다.

example(of: "dispose") {
	// 1
	let observable = Observable.of("A", "B", "C")

	// 2
	let subscription = observable.subscribe {event in
		// 3
		print(event)
	}
}
  1. String observable을 생성한다.
  2. observable을 구독한다. 이번에는 반환되는 Disposable의 이름을 subscription이라고 했다.
  3. 방출되는 이벤트를 출력한다.

구독을 취소하기 위해서 dispose()를 호출한다. 구독을 취소한 후에는 observable이 이벤트를 방출하는 것을 멈출 것이다.

subscription.dispose()

각 구독을 개별적으로 취소하는 것은 지루하고 반복적인 작업이 될 수 있기 때문에 RxSwift는 DisposeBag이라는 type을 제공한다. DisposeBag은 disposed(by:)를 통해 추가된 Disposable들을 가지고 있다가 자기 자신이 소멸할 때 각 Disposable의 dispose()를 한번에 호출해서 구독을 취소한다.

example(of: "DisposeBag") {
	// 1
	let disposeBag: DisposeBag()

	// 2
	Observable.of("A", "B", "C")
		.subscribe {
			// 3
			print($0)
		}
		// 4
		.disposed(by: disposeBag)
}
  1. DisposeBag을 생성한다.
  2. Observable을 생성한다.
  3. closure의 기본 파라미터 이름인 $0을 사용해서 event를 출력한다.
  4. subscribe에 의해 반환된 Disposable을 disposeBag에 추가한다.

observable을 생성하고 구독한 뒤 곧바로 dispose bag에 추가하는 이러한 패턴은 아마 RxSwift를 다루면서 가장 많이 사용하게 될 패턴일 것이다.

disposables 같은 귀찮은 작업을 왜 하는 것일까?

프로그래머는 항상 실수를 한다. 임의로 구독을 취소하게 하거나 필요한 경우에 dispose를 호출해서 observable을 종료시킬 수 있지만 프로그램이 커지고 이런 작업을 깜빡하게 된다면 메모리 누수가 발생하게 된다.

Swift 컴파일러는 사용되지 않는 disposable에 대해 warning을 주기 때문에 크게 걱정하지 않아도 된다.

지금까지 예제에서 우리는 특별한 next 이벤트를 가지는 observable을 생성했다. create operator는 observable이 호출하는 모든 이벤트를 만들 수 있다.

example(of: "create") {
	let disposeBag = DisposeBag()

	Observable<String>.create {observer in
	
	}
}

create operator는 subscribe라는 하나의 closure만 받는데 이 파라미터는 subscribe를 호출하는 구현을 제공한다. 다시 말해서 이 closure는 구독자(subscriber)들에게 방출되는 모든 이벤트를 직접 호출할 수 있다.

아직 모든 구현을 작성하지 않았기 때문에 Option-click으로 정보를 보려고 해도 정확한 정보를 얻을 수 없다.

subscribe 파라미터는 AnyObserver를 받아서 Disposable을 반환하는 escaping closure다. AnyObserver는 subscriber들에게 방출되는 값을 추가하기 용이한 제네릭 타입이다.

create 코드에 다음과 같은 구현을 추가하자.

Observable<String>.create {observer in
	// 1
	observer.onNext("1")

	// 2
	observer.onCompleted()

	// 3
	observer.onNext("?")

	// 4
	return Disposables.create()
}
  1. observer에 next 이벤트를 추가한다. onNext(:)는 on(.next(:))과 같은 의미다.
  2. observer에 completed 이벤트를 추가한다. 마찬가지로 onCompleted(:)는 on(.completed(:))와 같다.
  3. 또다른 next 이벤트를 추가한다.
  4. disposable을 반환한다. 예제의 경우 observable이 종료될 때 아무 작업을 안해도 되기 때문에 비어있는 disposable을 반환한다.
Note: 마지막 Disposable을 반환하는 부분이 이상해 보일 수 있다. subscribe operator는 반드시 disposable을 반환해야 하기 때문에 Disposables.create()로 disposable을 생성해서 반환했다.

두번째 onNext에서 ?를 방출했던 것을 기억하는가? 이 물음표는 subscriber에게 방출이 될까? 아니라면 왜 방출이 안될까?

정답을 알아보기 위해 위 create코드 뒤에 subscribe를 추가해서 구독해보자.

.subscribe(
	onNext: { print($0) },
	onError: { print($0) },
	onCompleted: { print("Completed") },
	onDisposed: { print("Disposed") }
)
.disposed(by: disposeBag)

observable을 구독하면서 기본 파라미터 이름을 사용해서 각 이벤트를 출력했다. 예상했던 대로 첫번째 next는 정상적으로 방출이 되어 출력되고, "Completed"와 "Disposed"가 출력된다. 두번째 next는 방출되지 않는데, 이미 completed를 방출하고 observable이 종료되었기 때문이다.

--- Example of: create ---
1
Completed
Disposed

error를 추가하면 어떻게 될까? 임의로 새로운 error type을 만들어보자.

enum MyError: Error {
	case anError
}

그 다음, observer.onNext와 observer.onCompleted 사이에 다음 코드를 추가한다.

observer.onError(MyError.anError)

이제 출력되는 내용이 바뀐 것을 볼 수 있다.

--- Example of: create ---
1
anError
Disposed

completed나 error도 호출하지 않고, disposeBag에도 추가하지 않는다면 어떻게 될까? 위 코드에서 observer.onError와 observer.onCompleted, disposed(by: disposeBag)을 모두 주석처리하고 실행해보자. 전체 코드는 이렇게 된다.

example(of: "create") {
	enum MyError: Error {
		case anError
	}

	let disposeBag = DisposeBag()

	Observable<String>.create {observer in
		observer.onNext("1")
		// observer.onError(MyError.anError)
		// observer.onCompleted()
		observer.onNext("2")
		return Disposables.create()
	}
	.subscribe(
		onNext: { print($0) },
		onError: { print($0) },
		onCompleted: { print("Completed") },
		onDisposed: { print("Disposed") }
	)
	//.disposed(by: disposeBag)
}

이 observable은 절대 끝나지 않고, disposable은 절대 소멸되지 않는다.

--- Example of: create ---
1
?

이 예제를 그대로 두고 앞으로의 예제 코드를 아래에 계속 추가해 나갈 예정이라면 메모리 누수를 막기 위해 onCompleted를 호출하는 부분과 dispose bag에 추가하는 부분의 주석을 해제하자.

 

 

 

Creating observable factories

단순히 subscribe 되길 기다리는 observable을 만드는 대신, 새로운 observable을 생성하는 observable factory를 만들 수도 있다.

example(of: "deffered") {
	let disposeBag = DisposeBag()

	// 1
	var flip = false

	// 2
	let factory: Observable<Int> = Observable.deffered {
		// 3
		flip.toggle()
		
		// 4
		if flip {
			return Observable.of(1, 2, 3)
		} else {
			return Observable.of(4, 5, 6)
		}
	}
}
  1. 어떤 observable을 생성할 지 구분할 수 있도록 플래그를 만든다.
  2. deffered operator를 사용해서 Int observable을 생성하는 factory를 만든다.
  3. factory가 subscribe될 때마다 플래그를 변경한다.
  4. flip 플래그에 따라 다른 observable을 생성해서 반환한다.

외부에서 봤을 때 보통 observable과 observable factory는 다른점이 없다. 반복문을 사용해서 factory를 4번 subscribe 해보자.

for _ in 0...3 {
	factory.subscribe(onNext: {
		print($0, terminator: "")
	})
	.disposed(by: disposeBag)
		print()
}

factory를 subscribe할 때 마다, 서로 다른 observable을 얻게 된다. 순서대로 123, 456, 123 ... observable을 얻게 된다.

--- Example of: deferred ---
123
456
123
456

 

 

 

Using Traits

Traits으로 필요에 따라 기존 observable보다 더 작은 범위의 observable을 생성할 수 있다. 기존 observable로도 충분히 같은 기능을 구현할 수 있지만 trait의 목적은 사용자 즉, 다른 프로그래머에게 정확한 목적을 전달하는 데 있다. trait을 사용한 코드는 보다 직관적인 가독성을 제공한다.

RxSwift에는 Single, Maybe, Completable 이렇게 세가지 종류의 trait을 제공한다.

Single은 success(value) 또는 error를 방출한다. success는 next와 completed의 조합으로, 데이터를 다운받거나 디스크에서 로드하는 것 처럼 한 번에 실행한 후 성공 여부를 방출해야 하는 경우에 사용할 수 있다.

Comletable은 completed 또는 error만 방출하고 다른 값은 방출하지 않기 때문에 파일 쓰기처럼 완료했는지, 실패했는지 여부만 중요한 작업에 사용할 수 있다.

마지막으로 Maybe는 Single과 Completable의 조합으로 success(value), completed, error(error)를 모두 방출할 수 있다. 작업의 성공 여부를 판단하지만 도중에 어떤 값을 방출할 필요가 있는 경우 Maybe를 사용할 수 있다.

앞으로 나올 챕터에서 trait에 대해 더 자세히 배울 수 있다. 하지만 지금은 Resources에 추가한 Copyright.txt 파일에서 텍스트를 읽어오는 예제를 통해 기본적인 사용 방법에 대해 배워 볼 것이다.

example(of: "Single") {
	// 1
	let disposeBag = DisposeBag()

	// 2
	enum FileReadError: Error {
		case fileNotFound, unreadable, encodingFailed
	}

	// 3
	func loadText(from name: String) -> Single<String> {
		// 4
		return Single.create { single in
			
		}
	}
}
  1. dispose bag을 만든다.
  2. 파일을 읽을 때 발생할 수 있는 에러에 대해 enum을 정의한다.
  3. 실제로 파일 읽기를 수행할 Single을 생성하는 함수를 정의한다.
  4. Single을 생성하고 반환한다.

create closure에 다음 코드를 추가한다.

// 1
let disposable = Disposable.create()

// 2
guard let path = Bundle.main.path(forResource: name, ofType: "txt") else {
	single(.error(FileReadError.fileNotFound))
	return disposable
}

// 3
guard let data = FileManager.default.contents(atPath: path) else {
	single(.error(FileReadError.unreadable))
	return disposable
}

// 4
guard let contents = String(data: data, encoding: .utf8) else {
	single(.error(FileReadError.encodingFailed))
	return disposable
}

// 5
single(.success(contents))
return disposable
  1. disposable을 생성한다. 각 단계에서 실패하면 closure를 종료해야 하는데 create closure는 disposable을 반환해야 하기 때문에 미리 생성한다.
  2. 파일 이름으로 부터 경로를 생성한다. 실패하면 error를 방출하고 disposable을 반환한다.
  3. 생성한 경로로 데이터를 읽는다. 실패하면 error를 방출하고 disposable을 반환한다.
  4. 읽은 데이터를 string으로 변환한다. 변환에 실패하면 error을 방출하고 disposable을 반환한다. (패턴이 보이기 시작하지 않는가?)
  5. 여기까지 성공했으면 success를 방출하고 disposable을 반환한다.

이제 함수를 동작하게 만들 수 있다.

// 1
loadText(from: "Copyright")
// 2
	.subscribe {
		// 3
		switch $0 {
		case .success(let string):
			print(string)
		case .error(let error):
			print(error)
		}
	}
	.disposed(by: disposeBag)
  1. loadText에 파일 이름을 전달하면서 호출한다.
  2. 반환된 Single을 구독한다.
  3. 이벤트를 switch로 분기한 뒤, 성공하면 읽은 데이터를, 실패하면 에러를 출력한다.

성공했다면 콘솔에서 파일의 내용을 볼 수 있을 것이다.

--- Example of: Single ---
Copyright (c) 2020 Razeware LCC
...

loadText에 전달하는 파일 이름을 잘못 입력해서 error가 정상적으로 출력되는지도 확인해보자.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함