[iOS/SwiftUI] ForEach 반복문의 \.id는 무슨 의미일까? - KeyPath 알아보기

이번 글은 지난 글에 이어서

ForEach문의 `\.self`, `\.id`의 의미에 대해 계속 알아보도록 하겠다.

 

Swift의 KeyPath 문법에 대해 알아보자.

 


Key Path 문법이란?

KeyPath 문법의 형태를 보면 \.~~ 이런 식으로 작성되어 있는데 

이는 컴퓨터에서 특정 경로를 표시할 때 본 적이 있을 것이다.

 

터미널에서도 특정 폴더로의 경로를 나타날때 백슬래시를 통해 표시하는 것을 볼 수 있다.

터미널에서 경로를 보여주는 방식

 

이와 마찬가지로 KeyPath는 어떤 특정한 타입이 가진 프로퍼티에 접근할 때 "경로를 통해서 접근"하는 것을 표현하는 방식이라고 볼 수 있다.

 

예를 들어 `User`라는 구조체가 있고, 그 안에 `name`이라는 String 타입의 변수가 있을 때 다음과 같이 접근할 수 있다는 뜻이다.

`\User.name` 이는 `KeyPath<User, String>`타입이 된다.

이는 name이라는 프로퍼티에 직접 접근하는게 아니라, 프로퍼티를 참조하는 형태로 접근한다.

 

이러한 KeyPath라는 개념이 등장한 배경은 다음과 같다.

 

  • 문자열 기반 KVC 한계 극복
    • Objectiv-C의 KVC(Key-Value Coding)에서는 속성을 "name" 이런 식으로 문자열로 지정했었다.
    • 이렇게 문자열로 작성할 경우 오타 발생시 런타임 오류 위험이 생긴다.
    • 그래서 KeyPath라는 개념이 등장했는데, 이는 "존재하지 않는 경로"를 컴파일 시점에 잡아주기 때문에 런타임 에러를 방지할 수 있었다.
  • 함수형 API 친화성
    • map, filter 등의 고차함수에는 원래 `users.map { $0.email }` 이런 식으로 프로퍼티에 접근했었다.
    • 이렇게 사용하는 대신 KeyPath를 이용해 `users.map(\.email)` 더 간결한 구문으로 사용할 수 있게 되었다.
    • 더 나아가 SwiftUI의 ForEach등에서 KeyPath를 통해 식별자를 지정하는 등 다양한 영역에 활용 가능해졌다.

 

즉, KeyPath라는 개념을 통해 이전보다 더 간결한 형태로 프로퍼티를 참조할 수 있게 되었다.

 


ForEach문에서 `\.self`와 `\.id`

이제 ForEach문에서 사용되는 `\.self`와 `\.id`의 의미에 대해 알아보도록 하자.

 

특정 데이터가 반복되는 형태에서 다음과 같이 id 즉, 암묵적 동일성을 위한 구분자를 지정할 수 있다.

ForEach(data, id: \.self) { element in
  // element 자신을 ID로 사용
}

ForEach(data, id: \.id) { element in
  // element.id 프로퍼티를 ID로 사용
}

 

먼저 `\.self`는 element 자체를 구분자로 사용한다는 의미이다.

이는 element 자체가 Hashable일 때 사용 가능하며, data가 Int, String의 배열 형태일 때 주로 사용된다.

 

반면 `\.id`는 element가 구조체 등의 타입일 때 사용하며, id라는 프로퍼티를 구분자로 사용한다는 의미이다.

여기서 id는 보통 UUID이거나, Hashable한 타입인 Int나 String이어도 사용 가능하다.

 

 

 

그런데 가끔 id라는 프로퍼티를 쓰지 않는 경우가 있는데,

이는 반복할 타입이 Identifiable 프로토콜을 채택한 경우이다.

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

let users: [User] = [...]

ForEach(users) { user in
  Text(user.name)
}

여기서 User라는 구조체는 Identifiable 프로토콜을 채택하고 있고,

이러한 타입은 내부적으로 `\User.id` KeyPath가 자동으로 지정되기 때문에 id 파라미터를 생략 가능한 것이다.

 

정리하자면

  • 구분자로 사용될 타입은 Hasable해야 한다.
  • 이 타입에 접근할 때 KeyPath타입의 특징 덕분에 존재하지 않는 경로일 때 컴파일 시점에 오류를 잡을 수 있다.
  • 또한 클로저 문법 대신 KeyPath 문법을 통해 코드를 더 간결한 형태로 만들 수 있다.
  • 이렇게 KeyPath로 각 뷰를 식별할 수 있고, SwiftUI는 안전하고 효율적으로 뷰를 그릴 수 있다.

 


*참고자료

https://woozoobro.medium.com/swift%EC%97%90%EC%84%9C-key-path-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-5956923a8976

 

Swift에서 Key-Path 표현식 쉽게 이해하기

우리 파인더를 보면 요런 식으로 폴더에 대한 경로를 볼 수 있죠?

woozoobro.medium.com

 

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions/?source=post_page-----5956923a8976---------------------------------------#Key-Path-Expression

 

Documentation

 

docs.swift.org

 

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0249-key-path-literal-function-expressions.md

 

swift-evolution/proposals/0249-key-path-literal-function-expressions.md at main · swiftlang/swift-evolution

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swiftlang/swift-evolution

github.com

 

https://80000coding.oopy.io/2c9f09c6-8162-4336-b8fe-a2d2d721f5ae

 

(Swift) 이름은 모르지만 어쨌든 "\." 에 대한 포스팅 (feat. KeyPath)

SwiftUI 튜토리얼을 따라하다가 아래와 같은 코드를 보게되었다.

80000coding.oopy.io