본문 바로가기

iOS/Swift

Swift) Metatype(.self, .Type, .Protocol) 정복하기 (1/2)

 

 

 

안녕하세요, 소들입니다 :D

오늘은 바로 2월 1일 설날이랍니다

눈 뜨자마자 갑자기 Metatype 포스팅이 하고 싶어져서

포스팅을 하고 있는 사람이라고 볼 수 있겠습니다

 

그 있잖아요 개발하다가 보면

 

self

.self

Self

.Type

.Protocol

 

막 ...  이렇게...  단어 하나갖고 장난질이여;;; 싶은 것들에 대해 

오늘은 짚고 넘어가보려고 합니당.. ...

 

조금 생소하고 어려운 내용일 수 있으나!

최대한 이해하기 쉽게 풀어쓸테니 같이 이해해보려고 노력해봅시다 :)

참고로 이 포스팅은 타입 / 인스턴스 멤버(프로퍼티/메서드)에 대한 개념이 있어야 이해할 수 있기에

만약 모르신다면 이 포스팅을 먼저 보고오시길 추천드립니다!

 

내용이 좀 길어져서...; 분명 1일에 쓰기 시작했는데 등록하는 날짜는 왜 2일이죠?

쨌든 이번 포스팅에선 .self / .Type에 대해 먼저 알아보고

다음 포스팅(내일 예정)에서는 .Protocol과 .self vs self vs Self에 대해 알아보도록 하겠음니다

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. Metatype이란 무엇일까

 

먼저 정의부터 보자면 매우매우 간단함

 

Metatype이란 타입의 타입을 나타낸다

 

..? 어쩔타입이요

타입의 타입이라.. 여러분 개발 하다가 가끔 자동완성 안 돼서 개빡칠 때

이런 장난질 좀 해보지 않았음?

 

 

sodeul.self.self.self.self.self.self
Human.Type.Type.Type.Type.Type.Type

 

 

이 난리..ㅎ

심지어 Type으로 끝낼 경우 뒤에 .self를 붙이라는 에러도 등장..

이 무한 호출이 가능한 바로 이것들이 Metatype과 관련이 있는 것들임!!!

그래서 도대체 Metatype이 뭔지에 대해 알아보러 가봅시다

 

 

 

 

2. Type : 타입을 받고 싶을 때 사용하는 

 

자, 먼저 Type에 대해 알아볼 것임

우리가 다음과 같이 만약 

 

 

struct Human {
    static let name = "sodeul"
    var age = 28
}

 

 

이런 Human이란 구조체를 만들고, 

안에 타입 프로퍼티인 name 인스턴스 프로퍼티인 age를 생성 했음

그럼 이둘의 호출 방법이 다른 것쯤은 당연히 알고 있잖음??

 

 

let sodeul = Human.init()
 
Human.name         // sodeul
sodeul.age         // 28

 

 

타입 프로퍼티의 경우 형식(Type)에 관련된 프로퍼티로 타입 이름만 알면 호출이 가능하고,

인스턴스 프로퍼티의 경우 인스턴스를 생성해야만 호출이 가능하잖음

 

자, 근데 여기서 한 가지 궁금점이 생겨봅시다

지금껏 타입 프로퍼티는 해당 타입의 이름을 알면 호출할 수 있었는데,

Human이란 타입 이름을 내가 모른다고 가정하면, name을 호출할 수 없을까??

 

아뉘!

바로 type(of:) 메서드를 사용할 수 있음

해당 타입의 인스턴스를 저장하는 게 아니라 해당 타입 자체를 저장할 수 있단 말임

 

 

let sodeulType = type(of: sodeul)      // Human.Type
sodeulType.name                        // sodeul

 

 

이렇게!!

sodeul이란 인스턴스를 type(of:)에 넣으면

바로 Human 타입 자체를 반환받아서 저장할 수 있음

또한 반환받아 저장한 sodeulType이란 변수는 Human 타입 자체이기 때문에

타입 프로퍼티인 name에도 접근할 수가 있는 것!!

 

자, 이때 이 "타입 자체"란 것이 "메타타입"인 것임!!!

Human이란 타입의 타입이다..!

아 갑자기 뭔 말인지 어지럽기 시작하네요?? 😱😱😱

 

다시 천천히 쉽게!! 이해해 봅시당

자, 처음에 우리가 sodeul이란 변수엔 Human 인스턴스를 넣었음

그럼 우린 보통 sodeul이란 변수의 타입을 얘기할 때 Human 타입이라고 얘기한단 말임?

 

자, 그럼 나는 인스턴스가 아닌 타입 자체를 저장할건뎅!!?? type(of:) 메서드를 이용해서?? 라고 해보셈!

이때 우린 타입 자체를 반환받은 sodeulType이란 변수의 타입을 똑같이 Human 타입이라고 말할 수 없음

왜냐? Human 타입은 Human이란 타입으로 생성한 인스턴스의 타입을 가리키는 거니까 😵‍💫

얘는 인스턴스가 아니라 정말 타입 자체인데?

 

자, 이때 이 타입 자체를 얘기하는 것이 바로 "메타 타입"인 것임

sodeulType은 Human이란 타입의 타입이다, 즉 Human의 메타타입이다 말임

타입의 타입이다.. 이 말이 이제 좀 이해가 감???

Human 타입의 타입 자체를 얘기하는 것다!

 

정리하자면,

 

Human 타입 ➡️ Human으로 생성한 인스턴스의 타입 (인스턴스 멤버 사용 가능)

Human 타입의 타입(메타 타입) ➡️ Human 타입 자체를 가리키는 타입 (타입 멤버 사용 가능)

 

이렇게 될 수 있음. 이제 이해 했져??

때문에 당연히 타입 자체를 가리키기 때문에 인스턴스 프로퍼티인 age엔 접근할 수 없음

 

 

 

 

이제 메타타입에 대해 감이 좀 왔을 거라 믿는데.. :D.. 침질ㅇ질..

자 이 메타타입을 표시하는 단어가 바로바로!!!

 

 .Type 

 

이란 것임ㅎㅎㅎㅎㅎ

Human의 메타타입은 바로 Human.Type으로 나타낼 수 있음!

 

여러분 메타타입은 뭐다?? 끝이 "타입"이다

따라서 메타타입은 한 마디로 그냥 우리가 흔히 사용하는 타입 중 하나임

뭐 자주 쓰는 타입 중에 String, Int를 타입으로 쓰잖음??

그것처럼 Human.Type도 하나의 타입 즉, 자료형이라고 보면 됨

 

아까 type(of:)를 다시 보자면

 

 

let sodeulTypeHuman.Type = type(of: sodeul)

 

 

이렇게 볼 수 있다는 것임

Human.Type은 Human의 메타타입으로, 

type(of:)를 통해 얻은 리턴 값의 타입은 Human.Type이다 라고 볼 수 있단 것

Human.Type을 어떤 하나의 "값"이라고 보면 안됨!!! 값이 아닌 Type이란 말임

따라서 위처럼 Type Annotation 가능한 것이고,

실제 파라미터의 타입을 메타타입으로 선언할 수도 있음 (바로 다음ㅇ ㅖ제)

 

 

 

2-1. 메타타입은 언제 사용하면 좋을까?

 

그럼 이제 이 메타 타입을 도대체 어떨 때 사용한단 말임??

generic 함수와 사용하면 아주 뭐랄까,,

인스턴스화를 시키거나 각 타입에 다라 작업 하게 할 수 있달까?

 

 

protocol Human {
    var job: String { get set }
    init(_ job: String)
}
struct Teacher: Human {
    var job: String
    
    init(_ job: String) {
        self.job = job
    }
}
struct Student: Human {
    var job: String
    
    init(_ job: String) {
        self.job = job
    }
}

 

 

만약 위와 같은 Human이란 프로토콜을 준수하는 Teacher, Student 구조체가 있을 때

다음과 같이

 

 

func create<T: Human>(type: T.Type-> T {
    switch type {
    case is Teacher.Type:
        return T.init("teacher")
    case is Student.Type:
        return T.init("student")
    default:
        fatalError("👾")
    }
}

 

 

이런 식으로 type이란 파라미터의 타입을 제네릭 T 타입의 타입, 즉 T의 메타타입을 받아서

해당 타입을 체크하여 각 타입에 맞는 작업을 한 인스턴스를 생성하여 반환시킬 수도 있딴 말!!

그리고 case를 통해 Teacher, Student란 타입의 타입, Teacher, Student의 메타타입과 비교를 하는 것

 

어렵지 않을 거라고 봄니댜...

이제 메타타입(.Type)에 대해 모두 이해했을 거라 생각하고 패쓰

 

 

 

 

3. self: 메타타입의 값을 얻어내기 위한

 

자, 아까 위 마지막 예제를 이어서 봅시당

create란 메서드를 호출시키기 위해서 다음과 같이 하면

 

 

let sodeul = create(type: Teacher.Type)     // Type 'Teacher.Type' cannot conform to 'Human'

 

 

자 이렇게 해주면 대차게 에러가 남

왜 에러가 나는지 1분만 생각해보셈

 

위에서 뭐라 했냐면, 메타타입은 하나의 "타입" 이라고 했음

지금 위 코드는 하나의 자료형을 파라미터 값으로 집어 넣은 것

 

 

let array = [IntString]

 

 

이 난리랑 비슷한 것..ㅎ 뭔짓이세요

그럼 메타타입을 값으로 얻어내기 위해선 어떻게 할까??

바로 이때 사용하는 것이 

 

.self

 

라는 키워드임!! :)

따라서 위 코드는 다음과 같이 바꿔주어야 하는 것

 

 

let sodeul = create(type: Teacher.self)     

 

 

해당 타입 이름에 대고 .self를 호출해주는 것임ㅎㅎ

짜잔!!!! 이렇게 하면 에러가 사라지고,

create란 메서드가 실행되고 난 후엔 sodeul이란 변수에 Teacher의 인스턴스가 들어가게 될 것임!

어렵지 않다!!

 

 

 

3-1.  Static Metatype vs Dynamic Metatype

 

엥 근데 아까 메타타입 만들 때 type(of:)로 만들 수 있다하지 않았나연?

이라고 한다면 정확히 짚으셨습니다

 

self를 이용해 만드는 것은 Static Metatype이고,

type(of:)를 이용해서 만드는 것은 Dynamic Metatype임!

 

둘의 차이점은, 뭐 눈치 챘겠지만

.self는 Static Metatype이라 컴파일 시점에 타입이 정해지고,

type(of:)는 Dynamic Metatype이라 런타임 시점에 타입이 정해짐

 

.self를 이용해서 메타타입 값을 얻어낼 경우,

 

 

Teacher.self.init("")

 

 

항상 타입에 대고 사용해야 하기 때문에 static으로 동작하는 거 같고

(모든 타입에는 .self가 들어있음)

 

 

let anyType: Any = 10
type(of: anyType)           // Int.Type

 

 

위와 같이 Any일 경우 컴파일 시점에는 타입이 정해지지 않고 런타임 시점에 정해지는데

이또한 가능한 거보면 type(of:)는 런타임 시점에 동작 하는 게 맞군요

 

보통 하위 클래스의 메타타입에 접근하려면 type(of:)를 사용하고,

그렇지 않으면 .self를 통해 static 메타타입에 접근하면 된다고 함니다

 

하위 클래스에 접근한단 ㄱㅓ 자체가 런타임 시점에 이뤄져야 하니까

(다운 캐스팅도 런타임 시점에 되는 마냥) 그래서 그런 거 아닐까 싶긴 하네용

 

 

흠,,,,,,,,,

갑자기 생긴 의문인디요,,,👀

Any.self도 가능한데... 그럼 이거도 Static이란 말이요..?

 

 

Any.self

 

 

Any는 런타임 시점에 타입이 정해질텐디.. 어케 self가 되는거지..

엉엉... 뭔데 뭐냐고

개꿀잼 몰카냐구 뭐냐구 엉엉

 

그리고.. 갑자기 생긴 의문2인디요,,, 👀

위에서 타입 이름에 대고 .self를 붙이는 거 자체가 메타타입의 값을 얻기 위해서였잖음?

근데 인스턴스를 생성했을 때도 인스턴스에 대고 self...가 가능한디 말이요...

 

 

Teacher.self.init("")

 

 

위처럼 타입 이름에 대고 .self를 붙일 땐 메타타입을 얻는 것은 ㅇㅋ라고 친다면,,

 

 

let sodeul = Teacher("")
sodeul.self.job

 

 

근데 위처럼 인스턴스에 대고 self를 붙여도 인스턴스 타입으로 작동 되는 거보면,

부를 당시에 타입이냐, 인스턴스냐에 따라 그냥 다른건가..?

그른건가.......????????????

????????????????????????????????????????????????????????

 

 

 

+ 재밌는 사실 

참고로 .self와 .Type은 무한 호출이 가능하기 때문에

 

 

sodeul.self.self.self.self.self.self
Human.Type.Type.Type.Type.Type.Type

 

 

이 난리가 가능한 거였답니다 호호

하나 호출하나 100개 호출하나 같음

 

 

 

+ 재밌는 사실 2

여러분은 사실 tableView를 만들 때 이 .Type과 .self에 대해 많이 써왔단 사실

 

 

let tableView = UITableView()
tableView.register(SodeulCell.self, forReuseIdentifier: "SodeulCell")

 

 

이 코드에서 SodeulCell.self가 바로 SodeulCell의 메타타입의 값을 넘겨주는 코드임

실제 register 코드에 들어가보면

 

 

 

 

타입이 이렇게 AnyClass라고 되어있는데 이 AnyClass가 바로

 

 

 

 

AnyObject.Type이라는 메타타입이기 때문에 .self로 넘겨줬던 것임ㅎㅎㅎ 낄낄

 

 

 

 

 

 

 

.

.

.

흠 일단 넘 길어지니까 읽기 힘들까봐 숨 좀 쉬라고 여기서 끊고

저 두 가지 의문들은... 우리 회사 시니어들 좀 굴려갖고.. 

답을 찾게되면 추가 하겠습니다..

 

2탄에서 만나요!

틀린 내용이나 궁금증은 언제든 댓글 남겨주세요 :)

 



Calendar
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
최근 댓글
Visits
Today
Yesterday