[Swift] 키패스 (KeyPath)

오늘 내용을 이해하기 위해서는 subscript에 대해 선행 지식이 있어야 이해가 쉬우실거에요

간단히 설명하자면 subscript는 배열을 사용하는 것처럼 [ ]을 사용하여 해당 인스턴스의 프로퍼티에 접근할 수 있게 만들어 준답니다.

좀 더 자세한 설명은 여기(subscript) 포스트를 참고해 주세요.

KeyPath?

KeyPath를 사용하면 프로퍼티의 참조를 저장할 수 있어요

그렇게 만들어진 keyPath를 subscript[keyPath:]에 매개변수로 전달하면 그 프로퍼티에 접근할 수 있죠!

그럼 먼저 생성을 알아 보자구요

\typeName.path(프로퍼티명)

keyPath를 생성할 때는 \로 시작하여 타입의이름.프로퍼티명으로 선언해주면 됩니다.

이렇게 선언된 keyPath는 컴파일을 거쳐 KeyPath 클래스의 인스턴스로 생성된다고 해요!

typeName

인스턴스명이 아닌 타입명입니다.

struct SomeStruct {
    var someProperty: Int
}

let sweetfood = SomeStruct(someProperty: 5)
let somePath = \SomeStruct.someProperty // o
let somePath = \sweetfood.someProperty // x

SomeStruct가 타입명이고 sweetfood는 인스턴스죠!

어떤 타입이든 사용할 수 있어요. 위의 예제에서는 구조체 타입이지만 Array타입도 가능하다는 거죠!

let firstPath = \[String].[0]

path

프로퍼티명, subscript, 옵셔널체인, 강제언래핑등의 표현식을 사용할 수 있습니다.

위 두개의 예제 코드에서는 .somProperty는 프로퍼티의 이름이고

[0]은 subscript입니다. 각 표현식은 밑에서 조금 더 살펴 보도록 할게요

사용

이렇게 생성된 keyPath는 어떻게 사용하는지 알아보자구요

struct SomeStruct {
    var someProperty: Int
}

let sweetfood = SomeStruct(someProperty: 5)
let somePath = \SomeStruct.someProperty // o

print(sweetfood[keyPath: somePath]) // 5

앞서 keyPath는 해당 프로퍼티의 참조를 저장하고 있다고 하였죠? 그럼 그 참조를 사용하면 값에 접근할 수 있겠죠!?

keyPath를 생성할 때 명시한 type의 인스턴스에서 [keyPath:] subscript를 사용하면 해당 인스턴스의 값에

접근할 수 있어요

간단하네요!

self 키워드 사용

keyPath에 self를 전달하면 자기 자신의 인스턴스를 참조할 수 있습니다.

var someTuple = (a: 10, b: 20)
someTuple[keyPath:\.self] = (a: 1, b:1)

이러한 .self를 IDKey라고 부른다네요?

그냥 someTuple = (a: 1, b: 1) 이렇게 해도 같은데.. 어떤경우 유용한지 아직은 잘 모르겠네요

중첩 인스턴스 사용

우리가 선언한 클래스, 구조체등에서 인스턴스 프로퍼티를 사용할 경우 그 인스턴스의 프로퍼티도 당연히 참조할 수 있겠죠?

struct InnerStruct {
    var name = "Inner"
}

struct OuterStruct {
    var name = "Outer"
    var instanceProperty = InnerStruct()
}

var outer = OuterStruct()
let innerNamePath = \OuterStruct.instanceProperty.name
print(outer[keyPath: innerNamePath]) // inner

subscript 사용

subscript의 매개변수가 Hashable 프로토콜을 준수할 경우 .path 부분에 subscript를 사용할 수 있어요.

뭔말인가 싶죠? 그냥 도트(.) 뒤에 배열에서 사용하는 [1], [2] 이런 subscript를 사용할 수 있단거에요!

대신 매개변수의 타입이 Hashable을 준수해야 하는 경우란 거죠.

Swift의 Int, String, Array같은 기본 타입들은 대부분 Hashable 프로토콜을 지원하고 있습니다!

let helloArray = ["hello", "hola", "bonjour", "안녕"]
let secondPath = \[String].[1]
helloArray[keyPath: secondPath] // hola

secondPath는 [String] 타입의 인덱스 1을 참조하겠죠?

그래서 [keyPath: secondPath]는 인덱스 1에 위치한 hola에 접근할 수 있습니다.

근데 이런 인덱스에는 변수로 접근할 수 있잖아요?

let helloArray = ["hello", "hola", "bonjour", "안녕"]
var index = 0
let secondPath = \[String].[index]
helloArray[keyPath: secondPath] // hello

이렇게요. 이때 index값을 수정하고 keyPath를 사용하면 어떻게 될까요?

let helloArray = ["hello", "hola", "bonjour", "안녕"]
var index = 0
let secondPath = \[String].[index]
helloArray[keyPath: secondPath] // hello
index += 1
helloArray[keyPath: secondPath] // hello

읭? index 값을 고쳤는데 왜 그대로 hello일까요?

index는 Int 타입이고 Int는 구조체입니다. 구조체는 바로 값 타입인거죠.

값 타입은 값이 복사되어 전달되어진다고 했죠? 네 그래서 추후에 수정을 하여도 secondPath의 [index]에는

영향을 주지 않기 때문입니다.

그럼 매번 keyPath를 생성해야 하나..하고 저는 생각을 했는데요.. (무식)

클로저를 사용하면 어떨까요!?

let helloArray = ["hello", "hola", "bonjour", "안녕"]
var index = 0
let secondPath = \[String].[index]

let fn: ([String]) -> String = { strings in strings[index] }
helloArray[keyPath: secondPath] // hello
fn(helloArray) // hello
index += 1
helloArray[keyPath: secondPath] // hello
fn(helloArray) // hola

클로저에서 사용하는 index가 캡쳐하고 그 index를 참조할 수 있죠. 그러면 index가 바뀔 때마다 바뀐값에

접근할 수 있구요!

옵셔널 체인 사용

let helloArray = ["hello", "hola", "bonjour", "안녕"]

print(helloArray[keyPath:\[String].first?.count]) // Optional(5)

간단해요 keyPath로 옵셔널 체인을 사용할 수 있다는거에요

keyPath를 어떻게 사용해야 유용한지 좋은지 잘 모르겠어요 아직은.

그래도 지식은 다다익선이죠..!

오늘도 하나 배워갑니다. 지식 + 1

댓글남기기