안드로이드 개발에서 우리는 자주 List를 사용합니다. 하지만 List는 Compose와 같은 최신 UI 프레임워크에서 안정적이지 않은 경우가 생길 수 있습니다. 때문에 ImmutableList나 PersistentList와 같은 대안 리스트가 등장했는데요, 이 글에서는 List와 불변 리스트의 차이점, 그리고 효율적인 상황에 따른 리스트 선택 방법을 다루어 보겠습니다!
List는 데이터를 저장할 수 있는 기본적인 컬렉션 타입으로, 읽기와 쓰기가 모두 가능합니다. 예를 들어, List<Int>로 여러 숫자를 저장하고 add()와 같은 메서드로 요소를 추가할 수도 있습니다. 이러한 List는 데이터의 추가, 삭제, 수정이 필요한 다양한 상황에서 유용하게 쓰이지만, 안드로이드 Compose와 같은 UI 프레임워크에서 불안정해질 수 있는 경우가 있습니다.
val items = listOf("Item1", "Item2", "Item3")
List는 가변성이 기본이기 때문에, Compose와 같은 UI 프레임워크가 리컴포지션 시 이 리스트를 안정적으로 캐싱하거나 불변 데이터로 간주하지 못하는 경우가 발생할 수 있습니다. 특히, UI 컴포넌트가 List에 의존하는 경우, 작은 변경이 전체 UI를 불필요하게 리컴포지션하도록 할 수 있습니다.
UI 컴포넌트가 List에 의존한다는 의미란?
UI 프레임워크에서는 화면을 다시 그릴 때 상태( 버튼 클릭 상태, 텍스트 입력 값 등..)이나 위치를 추적하여 재사용할 수 있습니다. 그러나 불변이 아닌 List를 사용하면 항목이 변경될 때마다 새로운 객체를 생성하고, UI요소를 효율적으로 추적할 수 없게 됩니다. 이로 인해 성능 문제나 의도치 않은 리컴포지션이 발생할 수 있습니다.
예를 들어볼까요?
val items = listOf("Item1", "Item2", "Item3")
items.removeAt(0) // List는 불변이므로 새로운 리스트가 생성되고 UI는 리렌더링됨
위 리스트는 불변이기 때문에 하나의 요소를 제거하면 리스트 항목을 직접 수정하지 않고 새로운 리스트를 생성합니다. UI요소가 불필요하게 다시 그려지기 때문에 성능 저하나 상태 추적의 문제를 일으킬 수 있게 됩니다.
리스트가 불변이 아니라는 점은 단순한 읽기 전용으로 보이지만, 실제로는 값 자체가 언제든 변경될 가능성이 있어 불안정할 수 있습니다. 따라서 안정성을 위해 불변 리스트를 사용할 필요가 있으며, 이런 경우 ImmutableList와 PersistentList가 해결책이 될 수 있습니다.
ImmutableList는 말 그대로 불변 리스트로, 한 번 생성되면 변경할 수 없습니다. 이 리스트는 내부의 요소들을 수정하지 못하게 하여, 불변성을 보장합니다. 따라서 데이터에 변동이 없는 리스트를 사용할 때는 적합합니다.
PersistentList는 불변 리스트와 비슷하게 요소를 수정할 수 없습니다. 그러나 구조적 공유(structural sharing)를 통해 기존 리스트를 효율적으로 재사용합니다.
예를 들어, PersistentList는 기존 리스트의 대부분을 공유하면서 변경된 부분만 별도로 관리하여, 리스트가 커져도 효율적으로 데이터를 추가하거나 변경할 수 있게 해줍니다.
// PersistentList 예시
val list = persistentListOf(1, 2, 3)
val newList = list.add(4) // [1, 2, 3, 4]
이 코드는 새 요소가 추가될 때마다 새로운 PersistentList를 생성하지만, 내부적으로는 이전 리스트를 재사용해 성능을 유지합니다.
PersistentList는 불변성 보장과 효율적인 요소 추가 및 변경이 필요할 때 적합하지만, 모든 상황에서 이를 사용할 필요는 없습니다. 간단하게 데이터를 임시로 저장하고 읽기만 할 경우나, 데이터가 자주 변경되지 않는 경우라면 기존의 List를 써도 충분합니다.
가장 먼저, PersistentList를 사용하려면 Kotlinx Collections Immutable 라이브러리를 추가해야합니다.
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5"
}
import kotlinx.collections.immutable.*
fun main() {
// PersistentList 생성
val list = persistentListOf(1, 2, 3)
// 요소 추가 (새로운 리스트 반환)
val updatedList = list.add(4)
println("기존 리스트: $list") // 기존 리스트: [1, 2, 3]
println("업데이트된 리스트: $updatedList") // 업데이트된 리스트: [1, 2, 3, 4]
}
위 예제에서 PersistentList는 list.add(4)를 통해 새로운 리스트를 생성하지만, 내부적으로는 list의 메모리를 대부분 공유합니다. 이러한 구조적 공유 덕분에 메모리를 절약하면서도 불변성을 유지할 수 있게되죠!
안드로이드 개발에서 List, ImmutableList, PersistentList는 각각의 장단점이 있고, 상황에 맞게 적절히 선택하는 것이 중요합니다. 자주 변동이 없는 데이터라면 ImmutableList가 적합하고, 데이터가 크고 빈번하게 변동이 필요한 상황이라면 PersistentList를 사용해 불필요한 리컴포지션을 방지하는 것이 좋겠네용