이 포스팅은 RxJavaについて調べた試した 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할수 있습니다.
RxJava Night를 위해 RxJava에 대해서 조사하기 시작한결과, 일본어의 정보가 거의 없어 힘들었기때문에 조사한 것을 정리했습니다.
RxJava와 Reactive Programming에 대해서 지금까지 전혀 몰랐기때문에, 내용에는 오류가 포함되어 있을 가능성이 있습니다. 사전에 양해를 구합니다.
기본 정보는 대부분
【翻訳】あなたが求めていたリアクティブプログラミング入門 - ninjinkun’s diary
입니다.
RxJava란 Reactive Programming을 하기위한 라이브러리인 Rx(Reactive Extensions)의 JVM판입니다.
Reactive Programming의 개념에 대해서 완전히 이해가 안됐으므로, 위의 기사를 참조해주세요.
StreamAPI + 비동기 콜백같은 느낌.
이벤트를 정의한 Observable 인스턴스에 대해 짧은 함수를 엮은(체인) 스트림같이 이벤트 결과 데이터를 가공하는 처리를 정의, 지연실행, 비동기 콜백을 할수있습니다.
처리를 싱행하고 결과를 전달하는 Observable, 결과를 수신할때의 처리를 정하는 Observer를 사용한다.
우선 “Hello”와 “world”라는 2개의 문자열을 결과로 전달하는 심플한 Observable를 생성해보자.
※java.util.Observable가 아니므로 주의
Observable<String> myObservable = Observable.create(
new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello");
subscriber.onNext("world!");
subscriber.onCompleted();
}
}
);
다음으로 결과를 수신받는 측인 Observer 생성.
Observer<String> myObserver = new Observer<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
System.out.println(s);
}
};
마지막으로 Observable의 subscribe 메소드에 Observer 전달하여 실행.
이 시점에 처음으로 Observable가 실행되고, onNext에 전달한 결과가 숫ㄴ차적으로 Observer에 전달한다.
myObservable.subscribe(myObserver);
그리고,
“Hello”
“world”
가 출력된다.
위는 친절한 예시이지만, 정의되어 있는 여러가지 메소드를 활용하는 것으로 좀 더 간결하게 기술할 수 있습니다. from은 수신받은 배열이나 Iterator의 요소를 순차적으로 결과로 반환하는 Observable를 생성해준다.
그리고, subscribe에는 Observer 대신으로 함수(Action1 인터페이스 인스턴스)를 1개만을 전달하는 것으로 onNext시에 함수를 실행해준다.
Observable<String> myObservable = Observable.from(new String[]{"Hello", "world!"});
myObservable.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});
Java8일아면 Action1은 함수형 인터페이스가 되므로 여기의 처리는 람다식으로 1줄이 됩니다.
Observable.from(new String[]{"Hello", "world!"}).subscribe(System.out::println);
map 메소드는 Observable를 별도의 Observable로 변환한다.
예를들면 조금전 Observable로부터 받을려는 것이 문자열 그대로가 아니라 문자열의 길이라고하자. map을 사용하면 문자열을 전달하는 Observable을 기반으로 문자열의 길이를 전달하는 새로운 Observable를 간단하게 생성할수 있다.
Observable.from(new String[]{"Hello", "world!"})
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
System.out.println(i)
}
});
이것은,
“5”
“6”
을 출력한다.
그럼 배열의 요소가 Nest한 경우는 어떨까요.
flatMap을 사용해 Nest한 요소를 병렬 결과로서 전달할수 있습니다.
여기서 flatMap의 Func1에서는 Observable반환하지만, flatMap은 이 Observable를 분해해 늘여놓은 1개의 Observable를 생성해준다.
// jekyll 문제로 다음라인은 변환이 필요합니다
// < --> {
// > --> }
String[][] helloAndGoodbye = <<"Hello", "world!">, <"goodbye", "world!">>;
Observable.from(helloAndGoodbye)
.flatMap(new Func1<String[], Observable<String>>() {
@Override
public Observable<String> call(String[] strings) {
// 이곳에 전달되는 것은 2개의 배열. 그것을 Observable해서 반환
return Observable.from(strings);
}
})
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
// 여기에 전달되는 것은 4개의 문자열
return s.length();
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
System.out.println(i)
}
});
“5”
“6”
“7”
“6”
이 출력된다.
복수 Observable를 합성하는 marge를 사용해 flatMap을 사용하지않고 적는것이 가능하다.
// jekyll 문제로 다음라인은 변환이 필요합니다
// < --> {
// > --> }
String[][] helloAndGoodbye = <<"Hello", "world!">, <"goodbye", "world!">>;
Observable.merge(Observable.from(helloAndGoodbye[0]), Observable.from(helloAndGoodbye[1]))
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
System.out.println(i)
}
});
filter를 사용하면 결과를 취사선택하는 것이 가능하다.
여기에서는 문자열이 “world!”와 일차하는것을 배제한다.
// jekyll 문제로 다음라인은 변환이 필요합니다
// < --> {
// > --> }
String[][] helloAndGoodbye = <<"Hello", "world!">, <"goodbye", "world!">>;
Observable.from(helloAndGoodbye)
.flatMap(new Func1<String[], Observable<String>>() {
@Override
public Observable<String> call(String[] strings) {
return Observable.from(strings);
}
})
.filter(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return !s.equals("world!");
}
})
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.length();
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
System.out.println(i)
}
});
“5”
“7”
이 출력된다.
여기까지를 Java8의 람다식으로 적으면 아래처럼 된다.
// jekyll 문제로 다음라인은 변환이 필요합니다
// < --> {
// > --> }
String[][] helloAndGoodbye = <<"Hello", "world!">, <"goodbye", "world!">>;
Observable.from(helloAndGoodbye)
.flatMap(Observable::from)
.filter(s -> !s.equals("world!"))
.map(String::length)
.subscribe(System.out::println);
이름이 없는 클래스가 줄지어 있으면 솔직히 그다지 보기쉬운 코드가 아니지만, 람다식으로 하면 if나 for를 많이사용하는 처리를 기술하는 것과 비교해 꽤 직관적인 코드가 되네요.
Wiki를 참조하면 알거라고 생각이듭니다만, RxJava에는 이외에도 상당한 수의 편리한 함수가 정의되어 있습니다.
예시와 같은 비교적 단순한 처리에서는 그다지 고마움을 느낄수 없지만, 데이터의 가공이 복잡할수록 효과를 발휘할것 같습니다.
또한, RxJava에서 처리를 작성하면 자연스럽게 처리의 단위가 작아지고 입력/출력이 명확하게 되며, 메소드로서 분리해서 테스트 작성이 쉽게되는 장점도 있을듯합니다.
RxJava에는 Android용 모듈도 정의되어있으므로, 실제 Android 어플리케이션의 유즈 케이스에 맞춘 예시도 작성해볼려고 생각합니다.
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024