안녕하세요, 소들입니다 :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 sodeulType: Human.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 = [Int, String]
|
이 난리랑 비슷한 것..ㅎ 뭔짓이세요
그럼 메타타입을 값으로 얻어내기 위해선 어떻게 할까??
바로 이때 사용하는 것이
.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탄에서 만나요!
틀린 내용이나 궁금증은 언제든 댓글 남겨주세요 :)
'iOS > Swift' 카테고리의 다른 글
Swift) closure와 @escaping 이해하기 (13) | 2022.07.18 |
---|---|
Swift) Metatype(.self, .Type, .Protocol) 정복하기 (2/2) (2) | 2022.02.02 |
Swift) Comparable에 대해 알아보자 (6) | 2022.01.30 |
Swift) Hashable에 대해 알아보자 (11) | 2022.01.25 |
Swift) Equatable에 대해 알아보자 (12) | 2022.01.14 |