티스토리 뷰

Swift

dynamicMemberLookup

Zedd0202 2021. 3. 11. 23:15
반응형

 

안녕하세요 :) Zedd입니다.

오랜만에 노래추천!

저스틴 비버 신곡! 뮤비보면서 눈물 줄줄..🥲

 

암튼 들으면서 dynamicMemberLookup정리 

 

dynamicMemberLookup


[특징]

✔️ SE-0195 에서 처음 소개. Swift 4.2에서 구현됨. (참고 Swift 4.2 변경사항)

✔️ class, struct, enum, protocol에 사용 가능

✔️ subscript (dynamicMemberLookup :) 메소드를 구현해야함.

 

dynamic / Member / Lookup

직역하자면 동적 / 멤버  / 조회인데..

동적으로 멤버들을 조회할 수 있게 만드는 듯한..그런 느낌이 옵니다.

 

1. 만약 dynamicMemberLookup을 "사용"하는 타입이 있다면,

2. 이 타입은 런타임시 확인되는 임의의 "name"에 대해 

3. "dot" syntax를 사용할 수 있도록 제공해준다.

 

도대체 이게 몬 소리야

 

# Subscripts

혹시 Swift의 Subscripts에 대해서 아시나요? 직역하자면 "첨자"라고 이야기 할 수 있습니다. 

이 첨자를 사용하여 index로 값을 설정하고, 값을 가져올 수 있습니다. 

var arr = ["Zedd", "Alan Walker"]
arr[0] // Zedd
arr[1] = "Marshmello"

우리에게 너무나도 익숙한 문법입니다.

이것이 가능한 이유는, Swift의 Collection이 subscript를 요구하고 있기 때문입니다.

public protocol Collection: Sequence {
	...
	subscript(position: Index) -> Element { get } 
    
}

배열, Dictionary, Set같은 친구들은 Collection 프로토콜을 준수하고 있기 때문에, Subscripts로 접근이 가능한 것입니다.

 

하지만 

struct Contact {
    
    var persons: [String: String]
}

let contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact["지구"] // ❌ Error! Value of type 'Contact' has no subscripts

제가 Contact라는 struct를 정의하고, 사는 곳 이름이 담긴 Dictionary를 persons에 넣어줬습니다.

그리고 contact[사는 곳] 이렇게 접근하려고 하지만, 에러가 나는 것을 볼 수 있습니다.

Value of type 'Contact' has no subscripts

이때 subscript를 타입 내부에 구현해주면 됩니다.

struct Contact {
    
    var persons: [String: String]
    
    subscript(planet: String) -> String? {
        return self.persons[planet]
    }
}
let contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact["지구"] // Zedd

이렇게요.

위 배열에서 봤던 예제로 동일하게 subscript로 set연산도 가능합니다.

subscript(index: Int) -> Int {
    get {
        // Return an appropriate subscript value here.
    }
    set(newValue) {
        // Perform a suitable setting action here.
    }
}

subscript는 연산 프로퍼티와 동일하게..get-only면 get구문을 생략할 수 있거든요. set역시 존재합니다.

Contact에 적용시켜볼게요.

struct Contact {
    
    var persons: [String: String]
    
    subscript(planet: String) -> String? {
        get {
            return self.persons[planet] ✅
        } set {
            self.persons[planet] = newValue ✅
        }
    }
}

var contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

var zedd = contact["지구"] // Zedd
contact["지구"] = "Alan Walker"
let walker = contact["지구"] // "Alan Walker"

이렇게 할 수 있습니다.

 

아니 그런데...dynamicMemberLookup이야기 하다가 갑자기 subscript이야기는 너무 뜬금스럽나요? 

dynamicMemberLookup이 subscript와 유사하여 아주 간단하게 살펴봤어요.

 

그렇다면 dynamicMemberLookup를 사용해보겠습니다.

 

# dynamicMemberLookup

1. 만약 dynamicMemberLookup을 "사용"하는 타입이 있다면,

2. 이 타입은 런타임시 확인되는 임의의 "name"에 대해 

3. "dot" syntax를 사용할 수 있도록 제공해준다.

 

✔️ 1. 만약 dynamicMemberLookup을 "사용"하는 타입이 있다면,

이를 위해 이 타입이 dynamicMemberLookup를 사용한다고 알려주어야 합니다.

이는 @dynamicMemberLookup attribute를 타입 앞에 붙이면 됩니다. 

@dynamicMemberLookup ✅
struct Contact {
    
    var persons: [String: String]
    
    subscript(planet: String) -> String? {
        return self.persons[planet]
    }
}

이렇게 말이죠. (set 연산은 일단 지울게요)

자..그럼 에러가 날거에요.

 

왜냐하면 dynamicMemberLookup이 요구하는 메소드가 있기 때문입니다. 

우리가 위에서 봤던 subscript메소드를 요구하는데.. dynamicMember라는 파라미터를 가진 메소드를 요구하네요.

수정해주겠습니다. 

@dynamicMemberLookup
struct Contact {
    
    var persons: [String: String]
    
    subscript(dynamicMember planet: String) -> String? {
        return self.persons[planet]
    }
}

여기까지는 이해하셨죠?

자..그럼 빌드하고 아까 우리가 작성했던 코드들을 테스트해봅시다.

var contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact[dynamicMember: "지구"] // Zedd

똑같은데, dynamicMember라는 파라미터를 같이 쓰도록만 바뀌었습니다. 동작에는 아무 이상이 없군요. 

 

✔️ 2. 이 타입은 런타임시 확인되는 임의의 "name"에 대해 

✔️ 3. "dot" syntax를 사용할 수 있도록 제공해준다.

이거는 한꺼번에 보겠습니다.

var contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact[dynamicMember: "지구"] // Zedd

이렇게 dynamicMember subscript를 이용해서 값을 가져올 수도 있지만!!!!!!

 

var contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact.지구 // Zedd 

이게 가능해진다는 것입니다.

 

만약 없는 값으로 부르면ㅇ

var contact = Contact(persons: [
    "지구": "Zedd",
    "달": "Marshmello"
])

let zedd = contact.화성 // nil

이렇게 nil이 나오게 됩니다. 

 

🙋‍♀️ Q : 지구...? Contact에 지구라는 프로퍼티가 있었어?

👩‍💻 A :

@dynamicMemberLookup
struct Contact {
    
    var persons: [String: String]
    
    subscript(dynamicMember planet: String) -> String? {
        return self.persons[planet]
    }
}

네 없죠. 없는데,

이 dynamicMemberLookup이 subscript를 우리에게 친숙한 dot syntax로 호출할 수 있도록 도와주는 것이죠.

 

사실 

contact[dynamicMember: "지구"] // Zedd
contact[dynamicMember: "화성"] // nil

contact.지구 // Zedd
contact.화성 // nil

위나 아래나 똑같습니다.

그래서 SE-0195 에서도 dynamicMemberLookup을 syntactic sugar로 설명합니다.

(This provides syntactic sugar that allows the user to write..)

 

🙋‍♀️ Q : 이게 어떻게 가능한지...

👩‍💻 A : 컴파일러가 subscript를 평가할 때, subscript가 "런타임"에 동적으로 호출되기 때문에 가능.

(왜 "dynamicMemberLookup"인지 아시겠나요!!) 

 

🙋‍♀️ Q : subscript(dynamicMember: )에 String타입 말고 다른 타입을 넣고싶어!

👩‍💻 A : ExpressibleByStringLiteral (프로토콜) 또는 keyPath만 넣을 수 있습니다.

String은 

ExpressibleByStringLiteral을 conform하고 있기에 들어갈 수 있는 것입니다.

ExpressibleByStringLiteral를 conform하고 있는 다른 타입들은 

이 있습니다. (참고 : ExpressibleByStringLiteral)

 

KeyPath를 넣는 방법이 있는데..이건 다른 글에서 공부하도록 할게요 ~.~

 

참고 

docs.swift.org/swift-book/ReferenceManual/Attributes.html

 

반응형

'Swift' 카테고리의 다른 글

Introducing Swift Collections  (0) 2021.04.13
dynamicCallable  (0) 2021.03.18
Property Wrapper  (2) 2021.03.07
Key-Value Observing(KVO) in Swift  (2) 2021.03.01
Key-Value Coding(KVC) / KeyPath in Swift  (4) 2021.02.28