[AAC] LiveData postValue 톺아보기

이태훈·2021년 7월 27일
1
val test1: MutableLiveData<Int> = MutableLiveData()
val test2: MutableLiveData<Int> = MutableLiveData()

test1.postValue(1)
test2.postValue(1)
test1.postValue(2)
test1.postValue(3)

viewModel.test1.observe(viewLifecycleOwner) {
    println("1: $it, ${viewModel.test2.value}")
}

viewModel.test2.observe(viewLifecycleOwner) {
    println("2: ${viewModel.test1.value}, $it")
}

위와 같은 코드가 있습니다.

처음, 저의 기댓값은 다음과 같았습니다.

I/System.out: 1: 1, null
    	      2: 1, 1
    	      1: 3, 1

test1, test2 순으로 값이 할당이 되고, 나머지 차례대로 test1에 할당한 값은 최신 값이 할당이 되어진다.. 라고 생각을 했습니다.

하지만, 결과값은 다음과 같았습니다.

I/System.out: 1: 3, null
    	      2: 3, 1

제 예상과는 꽤나 다른 결과값이 나와 postValue의 원리가 궁금해졌습니다.

그래서 공부한 LiveData의 postValue라는 함수에 대해 나름 끄적여보겠습니다.

먼저, LiveData의 postValue의 내부를 보면 다음과 같이 작성되어있습니다.

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }

    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

여기서 중요한 것은 postTask 라는 변수입니다. 이 변수의 존재를 인지하고 있으면서 postToMainThread 라는 함수로 가보겠습니다.

@Override
public void postToMainThread(Runnable runnable) {
    mDelegate.postToMainThread(runnable);
}

private ArchTaskExecutor() {
    mDefaultTaskExecutor = new DefaultTaskExecutor();
    mDelegate = mDefaultTaskExecutor;
}

postToMainThread의 mDelegate는 기본 생성자에서 DefaultTaskExecutor로 할당됩니다. 이어서 DefaultTaskExecutor로 가보겠습니다.

@Override
public void postToMainThread(Runnable runnable) {
    if (mMainHandler == null) {
        synchronized (mLock) {
            if (mMainHandler == null) {
                mMainHandler = createAsync(Looper.getMainLooper());
            }
        }
    }
    //noinspection ConstantConditions
    mMainHandler.post(runnable);
}

DefaultTaskExecutor의 postToMainThread 함수입니다. 이 함수를 통해 LiveData에서 본격적인 postToMainThread가 실행됩니다.
여기서, mainLooper를 가지는 Handler를 생성하기 때문에 postValue가 mainThread가 아니어도 값을 할당할 수 있게 됩니다.

그럼, 본문에서 test1, test2, test1,... 순으로 값을 할당하는데 main looper의 messaging queue에 test1, test2, test1,... 순으로 runnable을 삽입하는 거 아닌가?로 오해할 수 있습니다.

다시 LiveData의 postValue로 가보겠습니다.

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }

    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

처음에 언급한 postTask가 mPendingData의 값을 NOT_SET과 비교하여 불 대수 값을 가집니다.
그리고, postTask가 false가 되면 main looper에 runnable을 전달하지 않고 mPendingData의 값만 갱신해주게 됩니다.

그리고, mPostValueRunnable을 보게 되면,

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};

초기화 되지 않은 newValue를 가지고 있다가, mPendingData를 할당해주고, setValue를 해주는 모습입니다.

여기서 mPostValueRunnable이 NOT_SET으로 값이 초기화되는 것을 볼 수 있습니다.

mPostValueRunnable이 처리되지 않고, messaging Queue에 계속 남아 있으면, 아무리 postValue를 해주어도, mPendingData 의 값만 최신 값으로 갱신 되며, messaging Queue에는 쌓이지 않습니다.

그래서, test1.postValue, test2.postValue, test1.postValue,... 순으로 코드를 짜도, main looper에 있는 messaging Queue의 Runnable은 distinct한 LiveData의 개수만큼 존재하고, 이미 존재하는 Runnable에 대해서는 최신 값만 할당이 됩니다.

profile
https://www.linkedin.com/in/%ED%83%9C%ED%9B%88-%EC%9D%B4-7b9563237

0개의 댓글