[Android, Kotlin] 변수나 인스턴스 값 변경 시 다른 값도 변경 되는 문제 해결

 

[문제 상황]

 

일정을 표시하는 task 인스턴스를 만든 후에  여러 날짜의 task 인스턴스를 동시에 만들어 주고,

그 데이터를 관리하는 코드를 짜고 있었다.

 

fun addTaskData(startNum:Int,endNum:Int,task: Task){
    for (i in startNum..endNum){
        currentMonthArr[i].apply {
            if (this.taskList == null) {
                this.taskList = ArrayList()
            }
            this.taskList!!.add(task)
        }

    }
    taskLiveData.value = currentMonthArr

}

 

시작일과 끝일이 주어지면 전달된 task 인스턴스를 시작일부터 끝일까지 추가해주는 방식이었다.

성공적으로 데이터가 추가 되었으나 큰 문제가 발생했다.

 

아래의 이미지와 같이 각 날짜에 같은 데이터가 추가되었는데,

문제는 하나의 task 데이터를 변경하면  시작일부터 끝일까지의 모든 task 데이터가 변경되는 것이었다.

 

etc-image-0etc-image-1

 

18일의 task 데이터를 변경하면 함께 추가된 17, 19일의 task 인스턴스도 변경된다.

 

변경한 데이터가 task A.per( 0 -> 25)라고 했을 때 함께 생성된 task B, task C 등도 per(0 -> 25)으로 함께 변경된 것이다.

 

이유는 깊은 복사를 구분하지 않았기 때문이다.

 

[문제 해결]

 

문제 해결 이전에 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)를 비교해보면,

 

얕은 복사는 객체를 복사할 때 객체의 주솟값을 복사하는 방법이다.

위에서 task 인스턴스를 생성하는 방식이 얕은 복사이다.

 

얕은 복사를 사용하면 주솟값이 복사되기 때문에 두 변수가 같은 주솟값을 참조하면서 같은 값을 공유한다.

 

깊은 복사는 객체를 복사할 때 객체의 주솟값이 아닌 전체 값을 복사하는 방법이다.

kotlin의 data class에서 제공하는 오버 라이딩 메서드 copy()를 통해서 깊은 복사를 진행할 수 있다.

 

하지만 기본형이 아닌 멤버 변수는 copy()를 통한 깊은 복사가 되지 않는다.

이를 해결하기 위해서 새로운 방법을 찾아야 했다.

 

<Gson을 활용한 방식>

 

Gson은 java object -> json으로 변환해주거나, json -> java object로 변환해주는 구글 라이브러리다.

 

이 라이브러리를 사용하기 위해서 의존성을 추가한다.

 

dependencies {
  implementation 'com.google.code.gson:gson:2.8.8'
}

 

Gson 라이브러리에서는 데이터 복사를 위해서 2가지 메서드를 사용한다.

 

1. fromJson : Json 데이터를 지정한 타입으로 변환

2. toJson : 지정한 타입의 데이터를 Json 형식으로 변환

 

toJson을 통한 data class의 값을 Json 형식으로 변환, fromJson을 통한 다시 data class 형식으로 

변환하는 방법을 통해서 깊은 복사를 진행할 수 있다.

 

우선 깊은 복사를 진행하기 위해서 데이터 클래스를 선언한다.

 

// 깊은 복사를 위한 클래스 선언
data class SampleClass(var task:Task)

 

SampleClass에 task 데이터를 넣어서 깊은 복사로 변환해주는 방식을 사용한다.

 

위의 두 함수를 이용해서 깊은 복사를 진행하는 함수를 데이터 클래스 내부에 선언한다.

 

fun deepCopy() : SampleClass{
    return Gson().fromJson(Gson().toJson(this),this::class.java)
}

 

깊은 복사를 진행하는 여러 가지 방법이 있지만 Gson 방식이 제일 편리한 것 같았다.

 

 이제 제작한 data class를 사용한다.

 

fun addTaskData(startNum:Int,endNum:Int,task: Task){
    for (i in startNum..endNum){
        val newTask = SampleClass(task).deepCopy()
        currentMonthArr[i].apply {
            if (this.taskList == null) {
                this.taskList = ArrayList()
            }
            this.taskList!!.add(newTask.task)
        }

    }
    taskLiveData.value = currentMonthArr

}

 

이전 방식과 다른 점은 data 클래스에서 Gson 라이브러리의 도움으로

깊은 복사를 진행한 인스턴스를 생성한다.

 

이후에 newTask data class 안의 task 인스턴스를 리스트에 적용시킨다,

 

etc-image-2etc-image-3

 

18일에 추가된 task 데이터를 변경해도 17일의 task 데이터에는 변함이 없음을 확인했다.

 

문제 해결!