[iOS/SwiftUI] ForEach 반복문의 \.id는 무슨 의미일까? - SwiftUI의 Identity

 

SwiftUI에서 ForEach를 통해 반복되는 UI를 구성할때 `id: \.self`나 `id: \.id`와 같은 코드를 본 적이 있을것이다.

나는 이 코드의 의미에 대해 궁금해졌다.

 

이번 글에서는 SwiftUI의 Identity(동일성)에 대해 알아보고, 다음 글에서는 KeyPath에 대해 알아보도록 하겠다.


SwiftUI의 Identity

SwiftUI가 뷰를 그리는 방식은 다음과 같다.

`@State`등의 Source of Truth가 되는 프로퍼티가 변화하면 body 프로퍼티가 다시 렌더링 되면서 View가 다시 그려진다.

 

즉, `@State` 변수의 값이 변경되면 뷰가 해당 값을 반영한 형태로 다시 그려지는 것이다.

그렇다면, 어떻게 같고 다른것을 구분할 수 있는 것일까?

 

이 때 등장하는 개념이 Identity이다.

SwiftUI Identity는 최적화 매커니즘을 통해 어떤게 같고, 다른지 구분하려고 하는 개념이다.

 

그리고 이 Idenetity는 두 가지로 구분되는데, `Structural Identity(구조적 동일성)`, `Explicit Indentity(암묵적 동일성)`이 그것이다.


Structural Identity(구조적 동일성)

 

구조적 동일성은 뷰의 구조와 위치를 기반으로 Diff 연산을 해서 달라진점을 구분한다.

즉, 상태에 따라 뷰가 바뀌는지가 구조적 동일성을 지키는지에 영향을 미친다.

 

다음 코드를 보자.

if genre == "로맨스" {
    Image(systemName: "star")
} else {
    Text("로맨스 아님")
}

이러한 뷰는 genre라는 상태에 따라 뷰의 구조와 위치가 변경될 수 있다.

즉, 이는 구조적 동일성 측면에서 최적화된 매커니즘이 아니라고 할 수 있다.

 

그 이유는 상태에 따라 뷰의 구조와 위치가 변경되므로 렌더링을 계속 하게 되므로, 비효율적인 렌더링이 일어나기 된다.

 

반면 다음의 코드는 구조적 동일성을 지키는 코드이다.

struct TamagochiView: View {
    
    @State var count = 100
    
    var body: some View {
        VStack {
            Text("몇개냐면요")
            Text("\(count)개")
            Text("추가하기")
                .wrapToButton {
                    count += 1
                }
        }
    }
}

이 코드는 count라는 상태가 변경되면 Texr("\(count)개")만 렌더링되고, 다른 뷰들은 렌더링이 되지 않는 구조적 동일성이 지켜지는 효율적인 코드라고 볼 수 있다.

 

만약 위와 같은 동작을 하지만, 상태에 따라 뷰를 분기처리하면 구조적 동일성에 있어 비효율적인 코드가 된다.

struct TamagochiView: View {
    
    @State var count = 100
    
    var body: some View {
        VStack {
            if count < 100 {
                Text("몇개냐면요")
            } else {
                Text("개수가 많아요")
            }
            
            Text("\(count)개")
            Text("추가하기")
                .wrapToButton {
                    count += 1
                }
        }
    }
}

count가 100개를 넘는 경우에 `개수가 많아요`라는 Text를 보여주는데,

100개를 넘지 않는 경우에는 `몇개냐면요`라는 Text가 변하지 않고 보여진다.

 

그러나, count가 올라갈때마다 게속 `if count < 100`이 조건문을 판단해야하기 때문에

Text가 변하지 않더라도 계속 `Text("몇개냐면요")`라는 뷰가 렌더링 된다.

이는 구조적 동일성에 있어서 비효율적이라고 볼 수 있다.

 


Explicit Indentity(암묵적 동일성)

암묵적 동일성은 개발자가 직접 구분되는 점을 조작하는 것으로, 각 뷰에 id를 부여해서 구분이 되도록 하는 개념이다.

 

다음의 코드를 보자.

ForEach(list, id: \.id) { item in
    EventView(
        image: "star",
        text: "\(item.name): \(item.count)"
    )
}

이렇게 ForEach문을 통해 뷰를 반복적으로 생성하는 경우에 id 즉, 구분자를 두고 각 뷰를 구분한다.

즉, id를 부여함으로써 "동일한 뷰의 경우 렌더링을 하지 않아도 된다"고 개발자가 직접 조작할 수 있다.

 

반복문에서는 안에 들어오는 컨텐츠들이 추가되거나, 삭제되는 등 변경될 수 있기 때문에 특히 구조적 동일성을 지키기 어렵다.

그래서 암묵적 동일성을 부여해서 효율적인 렌더링을 하는 것이다.

 

다음은 암묵적 동일성의 코드이다.

struct TamagochiView: View {
    
    @State private var list = ["가", "나"]
    
    var body: some View {
        VStack {
            ForEach(list, id: \.self) { item in
                Text("\(item)")
            }
            
            Text("리스트에 추가하기")
                .wrapToButton {
                    list.append("a")
                }
        }
    }
}

이 코드를 보면 `/.self`자기 자신 자체를 id로 사용해 각 뷰를 구분하면서 암묵적 동일성을 지킬 수 있다.

 


그렇다면 `\.self`는 문법적으로 어떤 의미일까?

여기서 KetPath라는 개념이 등장하게 되는데, 이에 대해서는 다음 글에서 계속 소개하도록 하겠다.