본 포스팅은 droidkaigi-rxjava 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
코드는 생략하고 슬라이드의 일본어 부분만 번역
했다는 점 양해바랍니다.
애매한 번역 단어
待ち合わせ処理
는 “그룹핑한 병렬처리가 완료시 후 처리” 으로 번역했습니다만, Promise의 all과 같은 느낌이라고 이해하시면 됩니다.내일부터 사용할 수 있는 RxJava 자주 사용하는 패턴
RxJava 입문기사를 읽고, 업무에서도 사용하고 싶으신 분
or
복잡한 작업을 하는 어플리케이션을 개발 도중 최근 구현방법의 예측하기가 어렵게 되었다고 느끼는 분
오늘은 RxJava를 사용한 API와의 비동기처리에 대해서 이야기합니다
왜 API와의 비동기처리로 주제를 좁혔는가
RxJava는 어떤 문제를 해결할 수 있는가
비동기 콜백 시대의 과제
getActivity() == null 문제 (Fragment내의 콜백처리로는 NullPointException)
비동기 콜백의 병렬/직렬화 등 복잡한 구현할 경우 가독성도 낮아지고, 사내에서도 모범 사례가 정해지지 않았다
RxJava를 사용하면
간단하게 처리가 LifeCycle에 bind 가능하다.
and
복잡한 작업에도 강력한 오퍼레이터를 사용하는 것으로 선언적(宣言的)으로 기술이 가능해져 가독성도 보장된다.
어느 레시피 서비스의 검색 화면을 작성하는 이야기를 예로 RxJava의 자주 사용하는 패턴을 몇 가지 소개합니다 (cookpad.com과는 전혀 관계가 없습니다)
먼저 비동기 콜백을 사용해서 검색화면을 구현해봅니다
예) 레시피를 검색하는 비동기 처리 코드
예) “스테이크” 레시피를 검색해서 리스트에 추가
예) “스테이크” 레시피를 검색 실패했을 경우
지금의 처리를 RxJava로 바꿔봅니다
RxJava의 클래스를 사용하기 위해서는 build.gradle에 Dependencies를 추가합니다
2016/02 현재 ver 1.1.1이 최신입니다. rxAndroid에 대해서는 추후에 소개합니다.
searchRecipe 함수를 Observable로 변환합니다
1. 2번째 파라매터의 callback를 제거하고 내부에 이름없는 클래스를 작성합니다
2.
반환 값을 Observable<List
이 시점에서는 컴파일 에러가 됩니다
3. 내부 처리를 Observable로 감쌉니다.
Observable은 Constructor가 은폐되어 있기 때문에, Observable#create 경유로 인스턴스를 만듭니다.
4. Observable 안에 로직을 작성합시다
5. subscriber에 이벤트를 통지하게 합니다
subscriber.onNext(recipes); // 이벤트 통지
subscriber.onCompleted(); // 모든 이벤트가 종료했다는 통지
subscriber.onError(t); // 처리 내부에서 실패했다고 통지
이걸로 Observable은 완성입니다
여담) 동기 메소드를 Observable할 경우
불필요한 스레드 생성을 피하고자, Observable 내부는 가능한 동기처리로 합시다
예) “스테이크” 레시피를 검색해서 리스트에 추가(Rx버전)
Observable#subscribe()에는 람다식을 전달하는 것도 가능하다
Java8을 사용할 수 있는 환경이라면, rx.functions.Action1과 rx.functions.Action0을 람다로 변경할 수 있습니다
실은 아직 제대로 움직이지 않습니다
정확하게는 Observable의 구현에 따라서 네트워크 처리가 UI 스레드에서 실행되어 Crash가 일어납니다.
Observable에 동기 실행 / 비동기 실행의 개념은 없다
→ 지정하지 않은 경우는 (기본적으로) 호출한 스레드에서 실행된다
→ Scheduler를 사용해서 명시적으로 실행 스레드를 지정합시다
지정하지 않은 경우의 Observable 실행 스레드 판단은 어려우므로, Scheduler는 매번 지정합시다
Scheduler에 의해 실행 스레드 제어
Observable#subscribeOn에 대해서
전체 처리가 Schedulers.io가 할당한 Worker Thread에서 실행된다
Schedulers.io()는 상한이 없는 스레드풀로부터 스레드를 배정해줍니다.
Observable#observeOn에 대해서
Worker Thread(1)에서 실행
UI Thread에서 실행
Worker Thread(2)에서 실행
다른 개발자가 혼란해 할 수도 있으므로, observeOn을 여러 번 호출하는 것은 그다지 사용하고 싶지 않네요
Scheduler 지정이 자주있는 패턴
Observable 및 map 등의 오퍼레이터가 Worker Thread에서 실행되고, subscribe에 전달된 Subscriber(Action)가 UI Thread에서 실행된다.
레시피 검색 구현으로 Scheduler 지정을 기술
이것으로, Observable + 호출까지 올바르게 동작하게 됩니다.
getActivity == null 문제
getActivity == null 문제는?
Fragment 내에서 비동기 처리가 반환되기 전에, Activity가 파기되면, 콜백내에서 해제되어 인스턴스에 접근할 경우 NullPointException가 발생한다
getActivity() == null 문제에 대응(비동기 콜백 버전)
RxJava로 개발할 경우, trello/RxLifecycle을 사용하여 대응합시다
Subscription을 이용방법이 일반적이지만, 신경 쓸 부분이 적은 RxLifecycle을 소개합니다
getActivity() == null 문제에 대응 (Rx버전)
RxActivity를 상속할 필요가 있습니다만, 기존의 BaseActivity에 추가하는 것도 간단합니다.
혹시, 레시피 검색결과가 반환되기 전에 이전 화면으로 간다면
subscribe내의 처리가 호출되지 않는다
Observable을 하는 것으로 복잡한 작업도 선언적으로 작성되었다
→ 시험 삼아 기능을 추가해봅시다!
신기능 추가 1
“2번째 영역에 키워드 연동형의 광고를 넣자”
키워드 연동형 광고의 사양
특정 키워드가 검색되었을 경우에만 2번째 영역에 광고로 변경된다
광고 정보는 별도 API로 Request할 필요가 있다
“스테이크”로 검색하면 표시된다
“계란”으로 검색해도 표시되지 않는다
좋지않은 예) 레시피와 광고를 각각 독립적으로 읽어 들여 표시시키는 경우
“스테이크”로 검색
실수로 TAP이 발생하기 쉬운 UI는 피하고 싶다
예) “스테이크”로 검색하면 2번째 영역이 관련된 광고가 된다
“스테이크”로 검색
RxJava에서의 병렬처리
combineLatest(Observable, Observable, Func2)
// 이 경우, 3이 다음 오퍼레이터로 전달
2개의 Observable 처리를 이용해서, 반환 값을 Func2에서 값을 합성하는 오퍼레이터
combineLatest과 Pair를 사용하여 그룹핑한 병렬처리가 완료시 후 처리
람다식의 인수와 메소드의 임시 변수가 일치하는 경우에는, 아래와 같이 메소드 Reference라는 문법을 쓸 수 있습니다.
예) “스테이크”의 레시피를 검색 + 관련 광고의 검색
// 광고정보를 취득하는 메소드라고 생각해주세요
pair.first // 레시피의 리스트
pair.second // 광고 정보
combineLatest의 대체로 zip 오퍼레이터를 사용할 수도 있습니다.
combineLatest를 사용시 주의점
예) 레시피 취득에 성공, 광고 취득에 실패한 경우
“스테이크”로 검색
RxJava에서의 에러 처리 (1)
onErrorReturn을 정의하므로, Subscriber에 error가 통지되지 않게 된다.
광고를 검색하는 Observable에 에러처리를 추가한다
예외가 발생한 경우에 빈 데이터를 반환
onError가 호출되지 않고, onNext에 빈 광고정보가 전달되게 된다
병렬처리하는 Observable에 실행 스레드 문제
searchRecipe의 처리가 종료한 후에 실행된다
combineLatest는 병렬처리하지만, 스레드가 블락되어서 병렬처리가 안 되는 경우도 있습니다.
내부의 Observable에도 subscribeOn을 지정해서, 각각에 별도 스레드를 할당한다
→ combineLatest의 내부가 병렬처리되게 되었다
갑자기 광고팀으로부터의 요청
“광고 API 서버는 일시적으로 불안정하므로, 2번까지 재요청 처리를 해주세요”
RxJava에서의 에러 처리 (2)
// 예외가 발생한 경우 한번만 Retry한다
광고를 검색하는 Observable에 추가로 수정한다(Retry 추가)
신기능 추가 2
“검색화면에 Like 버튼을 추가합시다”
Like 버튼 사양
Like API 사양
레시피 id의 리스트를 보내면 이미 보존되어있는 레시피 id가 반환된다
레시피 정보의 취득 흐름
RxJava에서의 비동기처리의 직렬화
// 레시피 리스트를 그대로 전달한다
// 레시피 리스트로부터 Like한 레시피 Id를 취득
// map에서 Like상태를 레시피 인스턴스에 반영시킨다(생략)
Observable.just(x)는 x를 Observable로 감싸는 메소드입니다
(추가자료) 직렬처리만을 연결하는 패턴
https://twitter.com/takuji31/status/700151575948316672 씨의 지적대로 map으로 연결해도 괜찮겠네요
레시피를 검색 + like 상황을 알아보는 메소드를 작성
// Like상태를 레시피 인스턴스에 반영시킨다(생략)
최종적인 검색 화면의 흐름을 확인해봅시다
검색 화면의 최종적인 흐름도
“스테이크”로 검색
레시피를 검색 & like 상황을 취득 + 관련 광고 검색
복잡한 처리라도 코드의 가독성이 보장되는 것이 RxJava를 사용하는 매력이죠
정리 + 보충
RxJava에서의 병렬처리
위의 예에서는 2개의 Observable의 대기하는 결합이지만, 2~9, N개의 대기하는 결합이 가능한 메소드가 있습니다
RxJava에서의 직렬처리
flatMap은 flatMap(Observable::from)으로 리스트를 각 요소를 분할시킬 때에도 사용합니다.
2개이상의 벙렬처리를 하는 경우
2개의 Observable를 combineLatest로 그룹핑하여 결합하는 경우에는 Pair로 값을 합성합니다만, 3개 이상의 경우에는 Pair를 확장한 Tuple3, Tuple4를 사전에 만들어서 이용하고 있습니다.
Tuple5도 잠시 있었지만, Tuple4까지 있으면 대부분의 요구 사항을 충족시킬 것입니다.
보다 많은 병렬처리를 하고 싶은 경우
앞서 기술한 대로 combineLatest를 사용하면 복잡한 처리를 병렬로 실행할 수 있지만, Android 환경에서는 최대 16 병렬까지만으로 제한되어 있습니다. 아래와 같이 SystemProperty로 변경이 가능합니다.
https://github.com/ReactiveX/RxJava/issues/1820 에서 자세하게 설명하고 있습니다
그 외 / Tips
특수한 용도의 Observable
Http Request등 한번만 이벤트가 오는 Observable
다른 언어에서 말하는 Promise와 같은 것
이번 슬라이드에서 Observable를 사용한 곳에는 전부 Single로 변경할 수가 있습니다.
특수한 용도의 Observable
POST Request등, 별도로 값이 반환되지 않는 경우의 Observable의 성공 or 실패만을 알고 싶은 경우에 사용한다. 값은 전혀 오지 않는다.
특별히 필요하지 않는데 Response이나 null을 전달한다면, Single이나 Completable를 잘 사용하고 싶겠네요
감사합니다 (친목회에서 많이 이야기 합시다)
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024