RxJava - Disposable Deep Dive!

개발/안드로이드 2021. 9. 17. 20:00 Posted by 아는 개발자

1. Disposable 클래스의 역할

 

 

RxJava 공식 문서에서는 Observable과 Observer의 관계를 위 그림으로 표현한다. Observable에서 데이터를 전달 할 때는 onNext() 함수가, 더이상 전달할 값이 없을 때는 onComplete() 함수가 마지막으로 에러가 발생하면 onError()가 호출되는 방식이다. 이런 설명 방식도 조금 디테일하게 분석해보면 Observable과 Observer 사이에 Disposable 객체를 추가하는 것이 조금 더 정확할 것 같다.

 

 

Disposable 객체는 Observable에서 노출할 자원을 갖고 있고 Observer에게 이벤트로 전달하는 객체다. 그래서 RxJava 내부 소스코드를 분석해보면 첫번째 그림에 보여진 Observabe -> Observer에서 호출되는 함수는 사실 Disposable를 구현한 클래스 객체 내부에서 호출되고 있다. Observable은 Disposable을 생성하기 전까지 스트림을 대신 관리해주는 클래스고 실질적으로 값을 보내는 작업은 Disposable 내부 클래스에서 실행되고 있다.

 

2. 짧은 RxJava 코드

 

구체적으로 설명하기 위해 짧은 RxJava 코드 실행시 생성되는 객체들의 연관관계를 그려봤다. 

 

 

1. Observable.Just 는 단일 아이템을 생성하는 Observable 객체다. 이 객체를 생성하면 ObservableJust가 생성된다. 

2. doOnNext는 앞서 받은 Observable 아이템을 처리하고자 생성하는 루틴인데, 스트림을 유지하고자 ObservableDoOnEach를 만들었다. doOnNext의 내부 루틴은 DoOnEachObserver 객체에서 처리한다. 

3. doOnError도 doOnNext와 마찬가지로 스트림을 유지하고자 ObservableDoOnEach를 만들었다. doOnErro 내부 루틴은 DoOnEachObserver에서 처리한다. 

4. subscribe()가 호출되면 아래 스트림부터 최상단 스트림까지 차례로 구독 관계가 형성된다. ObservableDoOnEach -> ObservableDoOnEach -> ObservableJust 순서로 subscribe가 재귀로 호출되면서 Observer 간의 구독 관계가 완성된다

5. 최상단 ObservableJust는 값 1을 발행하는데 이 이벤트는 ScalarDisposable 에서 담당한다. ObservableJust는 DoOnEachObserver 내부 onSubscribe 함수를 호출해서 스트림 간에 down/upstream 을 구축한다.

6. ScalarDisposable 내부에서는 DoOnEachObserver 내부 onNext 함수를 호출해서 값 1을 전달한다. 

 

3. ScalarDisposable

 

ScalarDisposable 에서 값을 전달하는 부분은 Runnable로 동작하게끔 구현되있다. Observable.just 형태의 스트림을 구독하면 매번 새로운 쓰레드가 생성되서 실행된다.

 

 

4. Memory Leak 가능성

 

다른 Disposable 를 구현한 클래스를 찾아보면 Observable의 역할에 따라서 Runnable인 경우도 있고 Scheduler로 돌리는 경우도 있다. Intervar 처럼 긴 시간 돌리는 작업이면 따로 dispose() 함수를 호출하지 않는 이상 쓰레드가 종료되지 않고 계속 실행된다. 이런 코드가 증가하게 되면 불필요한 쓰레드 개수가 늘어나 Memory Leak이 발생할 소지가 있다. RxJava()를 사용할 때 CompositeDisposable() 객체를 활용해서 dispose() 시키라는 이유가 여기에 있다.

728x90

'개발 > 안드로이드' 카테고리의 다른 글

RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04

RxJava dispose()

개발/안드로이드 2021. 9. 16. 20:00 Posted by 아는 개발자

이번 포스트에서는 RxJava를 사용할때 왜 dispose() 함수를 호출해서 메모리 정리를 해야하는지를 사례를 통해서 정리해보고자 한다.

 

class LeakActivity : AppCompatActivity() {
    private var disposable1 : Disposable? = null

    companion object {
        private const val TAG: String = "leak_activity_tag"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val observeSource = Observable.interval(1, TimeUnit.SECONDS)
        disposable1 = observeSource.subscribe { Log.d(TAG, "subscriber1 value: $it") }
        observeSource.subscribe { Log.d(TAG, "subscriber2 value: $it") }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroyCalled")
        disposable1?.dispose()
        disposable1 = null
    }
}

 

이 액티비티는 생성하면서 1초 마다 이벤트를 보내는 Observable을 생성하고 두개의 subscriber로 구독하고 있다. 그리고 종료될 때는 첫번째 subscriber만 구독 모델을 해지한다. Activity를 종료하기 전까지는 두 subscriber에서 동시에 로그가 출력되는데

 

2021-09-16 17:48:01.106 subscriber2 value: 0
2021-09-16 17:48:01.106 subscriber1 value: 0
2021-09-16 17:48:02.106 subscriber1 value: 1
2021-09-16 17:48:02.107 subscriber2 value: 1
2021-09-16 17:48:03.106 subscriber2 value: 2
2021-09-16 17:48:03.106 subscriber1 value: 2
2021-09-16 17:48:04.106 subscriber1 value: 3
2021-09-16 17:48:04.106 subscriber2 value: 3

 

액티비티를 종료하고 나면 subscriber2에서 계속 로그가 출력된다. 화면이 없어졌는데도 이벤트를 지속적으로 구독하고 있다.

 

2021-09-16 17:48:44.907 onDestroyCalled
2021-09-16 17:48:45.106 subscriber2 value: 44
2021-09-16 17:48:46.107 subscriber2 value: 45
2021-09-16 17:48:47.106 subscriber2 value: 46

 

심각한 것은 백버튼으로 앱을 종료한 후 다시 실행해도 계속 구독하고 있게 된다는 것이다.  아래 로그를 보면 subscriber2 로그가 두번씩 찍히는데 이것은 이전에 남아있는 액티비티에서 구독한 subscriber가 계속 출력되기 때문이다.

 

2021-09-16 17:50:58.964 subscriber2 value: 0
2021-09-16 17:50:58.964 subscriber1 value: 0
2021-09-16 17:50:59.106 subscriber2 value: 178
2021-09-16 17:50:59.964 subscriber1 value: 1
2021-09-16 17:50:59.965 subscriber2 value: 1
2021-09-16 17:51:00.106 subscriber2 value: 179
2021-09-16 17:51:00.963 subscriber1 value: 2
2021-09-16 17:51:00.963 subscriber2 value: 2
2021-09-16 17:51:01.106 subscriber2 value: 180
2021-09-16 17:51:01.963 subscriber2 value: 3
2021-09-16 17:51:01.963 subscriber1 value: 3
2021-09-16 17:51:02.106 subscriber2 value: 181

 

단발성 이벤트를 구독했다면 큰 문제는 되지 않는다 하지만 위 코드처럼 지속적으로 이벤트를 보낸다면 그리고 subscriber 내부에서 메모리 할당 작업이 포함돼있었다면 메모리 릭이 발생하게 된다. 

728x90

'개발 > 안드로이드' 카테고리의 다른 글

RxJava - Disposable Deep Dive!  (0) 2021.09.17
RxJava dispose()  (0) 2021.09.16
ListAdapter, DiffUtil  (0) 2021.08.20
Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04

IoC container and Bean

개발/spring 2021. 9. 6. 21:00 Posted by 아는 개발자

IoC Containner and Bean

 

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC 
container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

 

IoC Container는 Spring에서 객체 의존성을 대신 관리해주는 플랫폼이고 이 플랫폼 내에서 생성되고(instantiated) 조립되고(assembled) 관리되는(managed) 객체를 Bean 이라고 한다. 간단하게 Bean은 애플리케이션 내에서 존재하느 수 많은 객체인데 Spring Container에서 관리되고 있다고 보면 된다. Spring 을 사용하면 Spring IoC 컨테이너에서 객체의 생성과 의존성 주입을 관리 할 수 있다. 

Application Context

 

The interface org.springframework.context.ApplicationContext represents the Spring IoC container and is responsible for instantiating, configuring, and assembling the aforementioned beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It allows you to express the objects that compose your application and the rich interdependencies between such objects.

 

ApplicationContext 인터페이스를 통해 Spring IoC container를 만들수 있고, 이 인터페이스는 Bean 객체를 생성하고(instantiating) 설정하고 (configuration) 조립하는 (assembling)한다. 설정 metadata를 읽어서 이 작업을 처리하는데 meta data 포맷은 XML이나 Java 어노테이션 또는 자바 코드를 통해서 가능하다.  

 

Dependencies 

 

Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes, or the Service Locator pattern.

 

Dependency Injection은 객체 간의 의존성을 정의하는 작업인데 즉 다른 객체간 어떻게 일하는지 의존하는 관계를 객체의 생성자나 setter 함수를 통해서 정해줄 수 있다. Container는 Bean 객체를 생성할 때 이 의존성을 대신 주입해준다. 이 과정은 완전히 역전된 관계라서 Inversion of Control 이라고 부른다. Dependency Injection을 사용하면 코드가 깔끔해지고 디커플링도 효율적으로 수행할 수 있게돼 결과적으로 클래스가 테스트하기 쉬워진다.

 

DI 방법은 생성자를 이용하는 방법과 Setter 함수 기반이 있다.

 

 

https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html

 

5. The IoC container

The BeanFactory provides the underlying basis for Spring's IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring. The BeanFactory and related interfa

docs.spring.io

 

728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05

@Bean vs @Component

개발/spring 2021. 9. 6. 20:00 Posted by 아는 개발자

Spring IoC 컨테이너 내에서 관리하는 객체들을 Bean 이라고 하고 Bean으로 사용 될 수 있는 객체는 XML이나 코드상에서 지정이 가능하다. 어노테이션을 사용하는 경우 @Bean, @Conponent 같은 어노테이션을 이용해 Spring IoC 컨테이너에 클래스를 객체로 등록할 수 있다. 

 

@Bean 

 

@Bean 어노테이션은 method 레벨 어노테이션으로 개발자가 수정할 수 없는 3rd 라이브러리 객체를 IoC Container에 등록하고 싶을 때 사용한다. 예를 들면 아래 코드처럼 애플리캐이션 전체에서 공통적으로 사용하고 싶은 Kafka 클래스를 만드려는 경우 아래와 같은 코드로 Bean을 등록 할 수 있다. @Bean을 사용할 때는 @Configuration 어노테이션이 추가된 클래스 내부에서 생성 할 수 있다. 

 

@Configuration
public class KafkaConfig {
    ...
    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

 

@Component 

 

@Component는 개발자가 직접 작성한 클래스를 Bean으로 등록할 수 있는 방법이다. 아래 코드처럼 공통적으로 사용하고 싶은 클래스가 있다면 클래스 이름 위에 @Component 어노테이션을 붙여준다. 

 

@Component
class Utils {
   fun print() { }
}

 

차이점

 

@Bean과 @Component를 헷갈릴 수 있는데 Third party 라이브러리를 Bean으로 등록하는 경우에는 @Bean을 사용하고 그렇지 않은 경우에는 @Component로 사용한다고 생각하면 쉽다. 사실 이럴수 밖에 없는 것이 @Component는 클래스 이름 위에서 선언이 가능한데 Third party 라이브러리는 클래스 수정이 불가능하므로 method level 어노테이션인 @Bean을 사용할 수밖에 없다.

728x90

'개발 > spring' 카테고리의 다른 글

IoC container and Bean  (0) 2021.09.06
@Bean vs @Component  (0) 2021.09.06
Node.js vs Spring Boot  (5) 2021.03.13
Spring 테이블 칼럼이 아닌 필드 데이터 받아오기  (0) 2021.03.05

오브젝트 리뷰 - 7

카테고리 없음 2021. 8. 29. 13:34 Posted by 아는 개발자

다형성

 

다형성 

 

강제다형성은 언어가 지원하는 자동적인 타입 변환이나 사용자가 직접 구현한 타입 변환을 이용해 동일한 연산자를 다양한 타입에 사용할 수 있는 방식을 가리킨다.

 

매개변수 다형성은 제너릭 프로그래밍과 관련이 높은데 클래스의 인스턴스 변수나 메서드의 매개변수 타입을 임의의 타입으로 선언한 후 사용 시점에 구체적인 타입으로 지정하는 방식을 가리킨다. 

 

포함 다형성은 메시지가 동일하더라도 수신한 객체의 타입에 따라 실제로 수행되는 행동이 달라지는 능력을 말한다. 객체지향 프로그래밍에서 가장 널리 알려진 형태의 다형성이다. 포함다형성을 위한 전제 조건은 자식 클래스가 부모 클래스의 서브타입이어야 한다는 것이다. 상속의 진정한 목적은 코드 재사용이 아니라 닿령성을 위한 서브타입 계층을 구축하는 것이다

 

상속의 양면성 

 

상속의 목적은 코드 재사용이 아니라 프로그램을 구성하는 개념들을 기반으로 다형성을 가능하게 하는 타입 계층을 구축하기 위한 것이다. 타입 계층에 대한 고민 없이 코드를 재사용하기 위해 상속을 사용하면 이해하기 어렵고 유지보수하기 버거운 코드가 만들어질 확률이 높다. 

 

자식 클래스 안에 상속받은 메서드와 동일한 시그니처의 메서드를 재정의해서 부모 클래스의 구현을 새로운 구현으로 대체하는 것을 메서드 오버라이딩이라고 하고 부모 클래스에서 정의한 메서드와 이름은 동일하지만 시그니처는 다른 메서드를 자식 클래스에 추가하는 것을 메서드 오버로딩이라고 부른다. 

 

업캐스팅과 동적 바인딩 

 

부모 클래스 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것이 가능하다. 이를 업캐스팅이라고 한다. 

 

선언된 변수 타입이 아니라 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 결정된다. 객체지향 시스템이 메시지를 처리할 적절한 메서드를 컴파일 시점이 아니라 실행 시점에 결정하기 때문이다. 이를 동적 바인딩이라고 한다. 

 

반대로 코드를 작성하는 시점에 호출할 코드가 결정되는 경우. 컴파일 타임에 호출할 함수를 결정하는 방식을 정적 바인딩 또는 컴파일타임 바인딩이라고 부른다. 

 

상속대 위임 

 

자신이 수신한 메시지를 다른 객체에게 동일하게 전달해서 처리를 요청하는 것을 위임(delegation)이라고 부른다. 위임은 본질적으로 자신이 정의하지 않거나 처리할 수 없는 속성 또는 메서드의 탐색 과정을 다른 객체로 이동시키기 위해 사용한다. 이를 위해 위임은 항상 현재의 실행 문맥을 가리키는 self 참조를 인자로 전달한다. 이것이 self 참조를 전달하지 않는 포워딩과 위임의 차이점이다.

 

서브클래싱과 서브타이핑

 

타입 

 

프로그래밍 언어의 관점에서 타입은 호출 가능한 오퍼레이션의 집합을 정의한다. 객체지향 프로그래밍에서 오퍼레이션은 객체가 수신할 수 있는 메시지를 의미한다. 따라서 객체의 타입이란 객체가 수신할 수 있는 메시지의 종류를 정의하는 것이다. 객체지향 프로그래밍에서 타입을 정의하는 것은 객체의 퍼블릭 인터페이스를 정의하는 것과 동일하다.

 

타입 계층 

 

타입 계층을 구성하는 두 타입 간의 관계에서 더 일반적인 타입을 슈퍼타입이라고 부르고 더 특수한 타입을 서브타입이라고 부른다. 객체지향 프로그래밍에서 객체의 타입을 결정하는 것은 퍼블릭 인터페이스다. 일반적인 타입이란 비교하려는 타입에 속한 객체들의 퍼블릭 인터페이스보다 더 일반적인 퍼블릭 인터페이스를 가지는 객체들의 타입을 의미한다. 

 

서브클래싱과 서브타이핑

 

타입 계층의 의미는 행동이라는 문맥에 따라 달라질 수 있다. 올바른 타입 계층이라는 의미 역시 문맥에 따라 달라질 수 있다. 따라서 슈퍼타입과 서브타입 관계에서는 is-a 보다 행동 호환성이 더 중요하다. 어떤 두 대상이 is-a 라고 표현할 수 있더라도 상속을 사용할 예비후보 정도로만 생각해야한다. 

 

타입의 이름 사이에 개념적으로 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a 관계를 사용하지 말아야 한다. 행동 호환 여부를 판단하는 기준은 클라이언트 관점이다. 클라이언트 관점에서 두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다. 그렇지 않다면 두 타입을 타입 계층으로 묶어서는 안된다. 

 

서브클래싱은 다른 클래스의 코드를 재사용할 목적으로 상속을 사용하는 경우를 가리킨다. 구현상속 또는 클래스상속이라고 부른다. 서브타이핑은 타입 계층을 구성하기 위해 상속을 사용하는 경우를 가리킨다. 부모 클래스는 자식 클래스의 슈퍼타입이 되고 자식 클래스는 부모 클래스의 서브타입이 된다. 인터페이스 상속이라고 부르기도 한다.  서브타이핑과 서브클래싱을 나누는 기준은 상속을 사용하는 목적이다. 

 

서브타이핑 관계가 유지되기 위해서는 서브타입이 슈퍼타입이 하는 모든 행동을 동일하게 할 수 있어야 한다. 어떤 타입이 다른 타입의 서브타입이 되기 위해서는 행동호환성을 만족시켜야 한다. 

 

리스코프 치환 원칙 

 

올바른 상속 관계의 특징을 정의하기 위해 리스코프 치환원칙이 있다. 자식 클래스가 부모 클래스와 행동 호환성을 유지함으로써 부모 클래스를 대체할 수 있도록 구현된 상속관계만을 서브타이핑이라고 볼 수 있다. 

 

상속관계는 클라이언트 관점에서 자식 클래스가 부모 클래스를 대체할 수 있을때만 올바르다.  is-a 관계는 객체지향에서 중요한 것은 객체의 속성이 아니라 객체의 행동이라는 점을 강조한다. 

728x90