천원의 개발

iOS KeyPath 정리 본문

iOS&Swift🍎/iOS

iOS KeyPath 정리

천 원 2023. 12. 26. 09:25

안녕하세요. 천원입니다.

최근 TCA로 사이드 프로젝트를 진행하고 있는데 아래와 같이 \. 이런 문법이 종종 등장을 해서 정리하고자 이 글을 작성합니다.

AuthTextField(text: viewStore.binding(get: \.codeText, send: InViteCodeFeature.Action.textEditing), placeholder: "초대코드", error: viewStore.validCode) // TCA Binding

@Dependency(\.apiService) var apiServiced // Custom Dependency

 

먼저 Object-C에서 등장한 Key의 개념을 살펴보면

- Key는 문자열을 의미하고 Key값을 통해서 인스턴스의 프로퍼티에 접근하게 해주는 Objective-C에서 나온 개념이라고 합니다.

 

 

KVC(Key Value Coding)

  • 인스턴스의 프로퍼티에 접근할 때 Key의 문자열로 접근하는 방식
  • KVC는 Objective-C 런타임에 의존하므로 프로퍼티 앞에 @objc 붙여서 사용
  • Objective-C의 것이기 때문에 NSObject가 가지고 있으므로 NSObject의 서브클래스여야 가능
class Person: NSObject {
    @objc var name: String = "윤제"
}

let person = Person()
person.value(forKey: "name") // Optional(윤제)

person.setValue("천원", forKey: "name")
person.value(forKey: "name")

 

코드를 확인해 보면 dot syntax로 프로퍼티에 접근하는게 아니라 "name"이라는 문자열을 key값으로 name 프로퍼티에 접근을 한 모습입니다. 

 

KVO(Key Value Observing)

  • KVC와 동일하게 키값으로 프로퍼티에 접근하는데 Observing이 가능하다
  • @objc dynamic 을 붙여서 사용
class Person2: NSObject { 
  @objc dynamic var name: String = "윤제"
}

person2.observe(\.name, options: [.old, .new]) { instance, change in
  print(change.oldValue, change.newValue)
}
person2.name = "천원" //(윤제, 천원)

 

observe 매서드를 통해서 name의 변화를 관찰하고 변동 시에 작성해둔 print문이 실행되는 모습입니다. willSet 혹은 didSet과 유사한 역할을 수행할 수 있어 보입니다. observe 함수의 매개변수의 타입을 확인해 보면

KeyPath를 타입으로 \.name을 받는것을 알 수 있습니다. 이제 KeyPath는 무엇인지 확인해 보겠습니다.

 

KeyPath

  • Key 값으로 프로퍼티에 접근이 가능한 것 처럼 KeyPath를 통해서도 접근이 가능하다
  • KeyPath는 Root라는 타입(=Person타입)으로부터 구체적인 Value Type(프로퍼티타입)으로의 key의 경로를 의미

조금 더 설명을 보태자면 Person이라는 타입의 프로퍼티인 name 까지의 경로를 의미하는게 KeyPath라고 합니다. 코드로 확인해 보면

class Person {
    var name: String = "윤제"
}

let person = Person()
person[keyPath: \Person.name] // 윤제
person[keyPath: \.name] // Root 타입은 생략 가능

Person -> name 의 KeyPath를 통해서 name 프로퍼티에 접근한 모습입니다. 그런데 우리가 앞서 궁금증을 가졌던 \. 형태는 이렇게 Dictionary 같은 형태가 아니라 매개변수로 \.을 받아서 사용을 했잖아요? 아래처럼!

AuthTextField(text: viewStore.binding(get: \.codeText, send: InViteCodeFeature.Action.textEditing), placeholder: "초대코드", error: viewStore.validCode) // TCA Binding

@Dependency(\.apiService) var apiServiced // Custom Dependency

이제 KeyPath를 활용한 함수를 작성해 봅시다.

 

KeyPath 활용

struct PersonInfo {
  var name: String
  var age: Int
}

struct School {
  var kim: PersonInfo
  var han: PersonInfo
  
  func getKim() -> PersonInfo {
    return self.kim
  }
  func getHan() -> PersonInfo {
    return self.han
  }
}

let kim = PersonInfo(name: "김감자", age: 12)
let han = PersonInfo(name: "한고구마", age: 13)
let school = School(kim: kim, han: han)

print(school.getKim()) // PersonInfo(name: "김감자", age: 12)
print(school.getHan()) // PersonInfo(name: "한고구마", age: 13)

School 구조체 내부에 각각에 프로퍼티를 return 하는 매서드가 존재하여 getKim, getHan을 통해서 프로퍼티를 가져온 모습이지만.. 학생의 수가 많아지면 그만큼 매서드의 수도 많아져야겠죠 이를 개선하기 위해서 우리는 KeyPath를 활용해 보겠습니다.

 

extension School {
  func getChild(keyPath: KeyPath<Self, PersonInfo>) -> PersonInfo {
    self[keyPath: keyPath]
  }
}

print(school.getChild(keyPath: \.kim)) // PersonInfo(name: "김감자", age: 12)
print(school.getChild(keyPath: \.han)) // PersonInfo(name: "한고구마", age: 13)

getChild 라는 매서드의 파라미터로 KeyPath를 받아 사용한 모습입니다. 각각의 매서드를 사용하는 것 보다 재사용성이 올라간 모습이네요!

 

여기까지 KeyPath 정리였습니다.

 

 

출처:

https://developer.apple.com/documentation/swift/keypath

https://ios-development.tistory.com/982