궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFT/Grammar

Swift : 기초문법 [프로퍼티#5 - 키 경로 KeyPath]

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

본 게시글은 yagom님과Zedd0202님의 게시글을 참고하여 작성되었습니다.

 

키 경로 KeyPath

객체의 값을 바로 꺼내오는 것이 아닌, Key 또는 KeyPath 를 이용해서 간접적으로 프로퍼티 위치 참조나 데이터를 가져오거나 수정하는 방법이다.

 

키 경로를 사용해 간접적으로 특정 타입의 어떤 프로퍼티 값을 가리켜야 할지 미리 지정해 두고 사용 가능. 여기서 경로는 프로퍼티 이름이라고 생각하면 된다.

 

키 경로는 역슬래시( \ ) 와 타입, 마침표( . ) 경로로 구성

class Person {
    var name: String
   
    init(name: String) {
        self.name = name
    }
}

struct Stuff {
    var name: String
    var owner: Person
}
print(type(of: \Person.name)) //ReferenceWritableKeyPath<Person, String>
print(type(of: \Stuff.name)) //WritableKeyPath<Stuff, String>

키 경로는 기존 키 경로에 하위 경로를 덧붙일 수도 있다.

let keyPath = \Stuff.owner
let nameKeyPath = keyPath.appending(path: \.name)

또, 각 인스턴스 keyPath 서브 스크립트 메서드에 키 경로를 전달해 프로퍼티에 접근도 가능하다.

struct Address {
    var town: String
}

struct Person {
    var address: Address
}

let address = Address(town: "한옥마을")
let Seogon = Person(address: address)

let SeogonAddress = Seogon[keyPath: \.address] //Address(town: "한옥마을")
let SeogonTown = Seogon[keyPath: \.address.town] //한옥마을

키 경로 만드는 법

출처 : https://samwize.com/ WWDC 2017

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

struct Stuff {
    var name: String
    var owner: Person
}

let seogun = Person(name: "서근")
var iPhone = Stuff(name: "아이폰", owner: seogun)

let referenceWritableKeyPath = \Stuff.name

iPhone[keyPath: referenceWritableKeyPath] //아이폰

이런 식으로 키 경로를 만들어서 사용하고 싶은 곳에 사용 가능! 예를 들자면 아래와 같다

class Person {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

struct Stuff {
    var name: String
    var owner: Person
}

let seogun = Person(name: "서근")
let cheolsu = Person(name: "철수")

let iPhone = Stuff(name: "아이폰 12 mini", owner: seogun)
var Macbook = Stuff(name: "맥북프로", owner: cheolsu)
let airPod = Stuff(name: "에어팟 프로", owner: seogun)

let stuffNameKeyPath = \Stuff.name
let ownerKeyPath = \Stuff.owner

// ownerKeyPath의 owner은 class Person을 참조하고 있기때문에
// \Stuff.owner.name 과 같은 표현
let ownerNameKeyPath = ownerKeyPath.appending(path: \.name)

print(iPhone[keyPath: stuffNameKeyPath]) //아이폰 12 mini
print(Macbook[keyPath: stuffNameKeyPath]) //맥북 프로
print(airPod[keyPath: stuffNameKeyPath]) //에어팟 프로

print(iPhone[keyPath: ownerNameKeyPath]) //서근
print(Macbook[keyPath: ownerNameKeyPath]) //철수
print(airPod[keyPath: ownerNameKeyPath]) //서근

//키 경로와 서브스크립트를 이용해 프로퍼티에 접근해 값을 변경할 수 있음
//Macbook은 변수로 선언 했기 때문에 가능
Macbook[keyPath: stuffNameKeyPath] = "맥북 M1"
print(Macbook[keyPath: stuffNameKeyPath]) //맥북 M1
Macbook[keyPath: ownerKeyPath] = seogun
print(Macbook[keyPath: ownerNameKeyPath]) //서근

//반대로 상수로 선언한 타입과 읽기 전용 프로퍼티는 값을 바꿔줄 수 없음
iPhone[keyPath: stuffNameKeyPath] = "아이폰 13 pro"
//error : Cannot assign through subscript: 'iPhone' is a 'let' constant
seogun[keyPath: \Person.name] = "라쿤"
//error : 키 경로가 읽기 전용입니다.

KeyPath 종류

WritableKeyPath

struct Person {
    var name: String
}
struct Stuff {
    var owner: Person
}

let writableKeypath = \Stuff.owner.name

personname이 변수 이기 때문에 변경 가능한 모든 프로퍼티에 대한 read & write access를 제공하기 때문에 writableKeyPath가 된다. 만약 name이 상수이면 수정이 불가(Read-only) 하므로 WritableKeyPath가 아닌 일반적인 KeyPath 타입이 된다. 

ReferenceWritableKeyPath

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

class Stuff {
    var name: String
    var owner: Person
    
    init(name: String, owner: Person) {
        self.name = name
        self.owner = owner
    }
}

let seogun = Person(name: "서근")
var iPhone = Stuff(name: "아이폰", owner: seogun)

let referenceWritableKeyPath = \Stuff.name

iPhone[keyPath: referenceWritableKeyPath] //아이폰

위에서 사용한 코드는 class를 사용하고 있기 때문에 자동으로 ReferenceWritableKeyPath 타입이 됨. 

 

읽어주셔서 감사합니다🤟

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.