[Swift] Meta-type, self
self와 Meta-type
이론
self
, Self
는 무엇이고 Type.self
는 무엇인가에 대해 알아보려한다
일반적으로 self
는 현재 객체의 참조이다. 간단하게 자신의 인스턴스를 참조이다.
하지만 클래스의 static
, class
메소드에서는 인스턴스가 없기 때문에 self는 인스턴스를 참조할 수 없다.
class Networker {
class func whoAmI() { print(self) }
}
Networker.whoAmI() // Networker
class
, static
메소드에서 self
는 현재 타입의 값을 가진다
class
, static
메소드는 인스턴스에 존재하는 것이 아니라 타입 자체에 존재하기 때문이다.
생각해보면 이상한 점이 발견할 수 있다. self
를 포함한 Swift의 모든 값은 타입이 있어야한다.
변수에 저장하고 함수에서 반환할 수 있어야 한다.
그렇다면 class
, static
메소드에서 self
라는 프로퍼티(변수)를 가지고 있는 타입은 무엇일까?
답은 Networker.Type
이다.
Networker.Type
은 모든 Networker
의 하위 타입을 포괄하는 타입이다.
Int가 모든 정수 값을 가지고 있는 것 처럼 Int.Type은 모든 Int 타입 값을 가지고 있다.
이렇게 다른 타입을 가지고 있는 타입을 Meta-type이라고 한다.
class WebSocketNetWorker: Networker {
class func whoAmI() -> Networker.Type { self }
}
let type: Networker.Type = WebsocketNetWorker.whoAmI()
이 예에서 type
은 meta-type 변수이다.
meta-type은 Networker
타입뿐만 아니라 그 서브 클래스도 보유할 수 있다.
프로토콜의 경우 프로토콜의 meta-type은 프로토콜의 타입과 프로토콜을 채택한 모든 타입을 보유할 수 있다.
meta-type을 함수에 전달, 변수에 저장하여 사용하기 위해서는 타입명.self
를 사용해야 한다
사용
일반적인 타입에서의 사용
struct Medium {
static let author = "sweetfood-dev"
func postArticle(name: String) {}
}
let blog: Medium = Medium()
Medium()
: 인스턴스
Medium
: 타입
인스턴스명인 blog
로 postArticle()
을 호출할 수 있지만 static
프로퍼티
author
에는 접근할 수 없다
author
는 Medium.author
로 접근이 가능
인스턴스 변수를 사용하여 접근하기 위해서는 type(of:)
를 사용한다
type(of:)
를 사용하여 class 프로퍼티
에 접근이 가능함
print(type(of:blog).author) // "sweetfood-dev"
그렇다면 Medium
의 메타 타입은 무엇일까?
let metaType = type(of:blog)
metaType
에 option + 클릭을 하면 Swift가 추론하여 얻은 변수의 타입을 알수 있다
let metaType: Medium.Type
metaType
의 타입은 Medium.Type
이다.
본격적으로 metaType
을 사용해보자
let author = metaType.author
let instance = metaType.init()
instance.postArticle(name: author)
이렇게 meta-type을 가지고 init() 및 클래스 프로퍼티, 메소드등을 호출할 수 있다.
meta-type은 함수의 파라미터로 전달할 수도 있고 Generic과 함께 사용하면 더욱 강력해진다
func createWidget<T: Widget>(ofType: T.type) -> T {
let widget = ofType.init()
myWidget.insert(widget)
return widget
}
equality check에도 유용하게 사용할 수 있다
class BlogPost { }
class TutorialBlogPost: BlogPost { }
class ArticleBlogPost: BlogPost { }
class TipBlogPost: BlogPost { }
func create<T: BlogPost>(blogType: T.Type) {
switch blogType {
case is TutorialBlogPost.Type : print("Tutorial")
case is ArticleBlogPost.Type : print("Article")
case is TipBlogPost.Type : print("Tip")
default: print("error")
}
}
create(blogType: TutorialBlogPost.self)
create(blogType: ArticleBlogPost.self)
create(blogType: TipBlogPost.self)
모든 타입의 이름 되에 .Type
을 사용함으로써 meta-type 정의에 사용할 수 있다.
위 코드에서 blogType
의 타입은 T.Type
이지만 실제 호출할 때는
TutorialBlogPost.self
, ArticleBlogPost.self
와 같이 self
를 사용하였다
TutorialBlogPost.Type
으로 사용하면 어떻게 될까?
당연히 에러를 만나볼 수 있을 것이다.
.Type
은 String
, Int
와 같은 타입이고
.self
는 "abc"
, 5
와 같은 실제 값이기 떄문이다.
meta-type은 타입을 값으로 가지는 타입이기 때문에 값은 .self
로 넘겨주어야 한다.
테이블 뷰를 사용하다보면 아래와 같은 코드를 많이 사용해왔을 것이다
tableView.register(MyTableViewCell.self, forReuseIdentifier: "MyCell")
이 메소드의 원형을 보자
func register(_ cellClass: AnyClass?,
forCellReuseIdentifier identifier: String)
타입명.self
를 값으로 전달하는 인자는 타입.Type
으로 선언된 파라미터에 인자로 넘긴다고 하였는데
cellClass
는 옵셔널 AnyClass
로 되어있다. 이상하지 않은가? AnyClass
의 선언을 봐보자
typealias AnyClass = AnyObject.Type
AnyClass
는 AnyObject.Type
의 typealias
이란 것을 확인할 수 있다.
그래서 우리는 위 테이블뷰의 메소드 register(_: forReuseIdentifier)
를 사용할 때 meta-type의 값인
타입.self
를 넘겨 왔던 것이다.
프로토콜에서의 사용
이런 모든 사항이 프로토콜에도 적용이 된다
다만 유의해야할 사항이 있다
protocol MyProtocol {}
let metaType: MyProtocol.Type = MyProtocol.self // 에러
에러의 원인은 MyProtocol.Type
이 Protocol 자체의 meta-type을 뜻하는게 아닌
Protocol을 채택하고 구현한 타입의 meta-type을 뜻하고 있기 때문이다
즉, 아래와 같이 사용을 해야한다
protocol MyProtocol {}
struct SomeStruct: MyProtocol {}
let metaType: MyProtocol.Type = SomeStruct.self
이렇게 선언된 metaType
변수는 MyProtocol
에 정의되어 있는
class
, static
메서드 및 프로퍼티에 접근할 수 있다.
실제로 호출되는건 MyProtocol의 요구사항을 구현한 SomeStruct의 메서드 및 프로퍼티
댓글남기기