[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를 사용해야 한다

사용

[참조]

[참조2]

일반적인 타입에서의 사용

struct Medium {
    static let author = "sweetfood-dev"
    func postArticle(name: String) {}
}

let blog: Medium = Medium()

Medium() : 인스턴스

Medium : 타입

인스턴스명인 blogpostArticle()을 호출할 수 있지만 static 프로퍼티 author에는 접근할 수 없다

authorMedium.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으로 사용하면 어떻게 될까?

당연히 에러를 만나볼 수 있을 것이다.

.TypeString, 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

AnyClassAnyObject.Typetypealias이란 것을 확인할 수 있다.

그래서 우리는 위 테이블뷰의 메소드 register(_: forReuseIdentifier)를 사용할 때 meta-type의 값인

타입.self를 넘겨 왔던 것이다.

프로토콜에서의 사용

이런 모든 사항이 프로토콜에도 적용이 된다

다만 유의해야할 사항이 있다

protocol MyProtocol {}
let metaType: MyProtocol.Type = MyProtocol.self // 에러

에러의 원인은 MyProtocol.TypeProtocol 자체의 meta-type을 뜻하는게 아닌

Protocol을 채택하고 구현한 타입의 meta-type을 뜻하고 있기 때문이다

즉, 아래와 같이 사용을 해야한다

protocol MyProtocol {}
struct SomeStruct: MyProtocol {}
let metaType: MyProtocol.Type = SomeStruct.self

이렇게 선언된 metaType 변수는 MyProtocol에 정의되어 있는

class, static 메서드 및 프로퍼티에 접근할 수 있다.

실제로 호출되는건 MyProtocol의 요구사항을 구현한 SomeStruct의 메서드 및 프로퍼티

댓글남기기