SwiftUI로 리스트나 그리드 형태의 UI를 구성할 때 가장 먼저 마주하게 되는 개념 중 하나가 바로 Identifiable 프로토콜입니다. List나 ForEach에 컬렉션을 넘길 때는 요소가 Identifiable을 준수해야 하는데, 준수하지 않으면 컴파일 에러가 납니다. 예를 들어 'generic struct ForEach requires that User conform to Identifiable' 같은 메시지가 뜨거든요. 화면 갱신 시 엉뚱한 위치가 바뀌는 런타임 문제는 Identifiable 미준수 때문에 생기는 게 아니라, id로 인덱스나 중복·불안정한 값을 지정했을 때 발생합니다.

struct User: Identifiable {
    let id: Int
    let name: String
}

let users = [User(id: 1, name: "Alice"), User(id: 2, name: "Bob")]

위 코드처럼 모델에 id 속성을 포함하고 Identifiable 프로토콜을 채택하면 SwiftUI가 각 아이템을 고유하게 식별할 수 있습니다. 모델을 수정하기 어렵거나 외부 API 구조를 그대로 써야 한다면 ForEach에 id 파라미터로 키패스를 지정할 수 있습니다. 예를 들어 ForEach(users, id: \.userId) { ... } 처럼 쓰거나, 값 자체가 Hashable이면 ForEach(names, id: \.self) { ... } 형태로 지정할 수 있거든요. 실무에서는 가독성과 유지보수를 위해 모델 자체에 Identifiable을 적용하는 것이 더 권장됩니다.

import SwiftUI

struct Product: Identifiable {
    let id: String
    let title: String
}

let products = [Product(id: "p1", title: "iPhone"), Product(id: "p2", title: "iPad")]

struct ContentView: View {
    let items = products
    
    var body: some View {
        List(items) { item in
            Text(item.title)
        }
    }
}

주의할 점은 Identifiable에 사용하는 식별자 값은 반드시 중복되지 않는 고유한 값이어야 한다는 것입니다. 단순히 배열의 인덱스를 id로 사용하는 방식은 리스트 순서가 바뀌거나 아이템이 삭제될 때 SwiftUI가 애니메이션을 정확하게 처리하지 못하게 만드는 원인이 됩니다. 따라서 데이터베이스의 Primary Key나 서버에서 내려주는 고유 ID 값을 활용하는 것이 안전합니다. 리스트의 안정적인 렌더링과 매끄러운 애니메이션 효과를 위해 고유 식별자를 정확히 정의하는 과정이 필수적입니다.

이처럼 Identifiable을 올바르게 구현하면 데이터가 변경되었을 때 필요한 부분만 효율적으로 다시 그리는 성능 최적화 효과도 얻을 수 있습니다. 단순히 화면에 보여주는 것을 넘어 SwiftUI의 선언적 UI 원칙을 제대로 활용하기 위한 기초 단계라고 볼 수 있습니다.

+ Recent posts