Swift .self 완벽 가이드: 타입과 인스턴스를 값으로 다루기
소개
Swift에서 타입이나 인스턴스를 값으로 사용해야 하는 상황이 있습니다. 이때 .self
표현을 사용하여 타입 자체나 인스턴스 자체를 값으로 전달할 수 있습니다. 이 글에서는 .self
의 정확한 사용법과 실제 활용 예시를 알아보겠습니다.
1. 타입을 값으로 사용할 때 (메타타입)
기본 개념
타입 자체를 값으로 사용하려면 .self
를 사용하여 메타타입(Metatype)을 얻어야 합니다.
1
2
3
4
5
6
7
8
9
| // String 타입 자체가 할당된다.
let stringType: String.Type = String.self
// Int 타입 자체가 할당된다.
let intType: Int.Type = Int.self
// 타입 정보 출력
print(stringType) // String
print(intType) // Int
|
2. 제네릭에서 사용 예시
기본 제네릭 함수
1
2
3
4
5
6
7
| func getType<T>(type: T.Type) {
print(type)
}
getType(type: String.self) // String
getType(type: Int.self) // Int
getType(type: Double.self) // Double
|
제네릭 타입 정보 활용
1
2
3
4
5
6
7
| func getTypeName<T>(type: T.Type) -> String {
return String(describing: type)
}
let stringName = getTypeName(type: String.self) // "String"
let intName = getTypeName(type: Int.self) // "Int"
let personName = getTypeName(type: Person.self) // "Person"
|
제네릭을 활용한 팩토리 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| protocol Initializable {
init()
}
extension String: Initializable {
init() {
self = ""
}
}
extension Int: Initializable {
init() {
self = 0
}
}
func createDefault<T: Initializable>(type: T.Type) -> T {
return type.init()
}
// 사용 예시
let defaultString = createDefault(type: String.self) // ""
let defaultInt = createDefault(type: Int.self) // 0
|
3. 실제 활용 예시
JSON 디코딩에서의 활용
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
| import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
}
struct Post: Codable {
let id: Int
let title: String
let content: String
}
func decode<T: Codable>(_ type: T.Type, from data: Data) -> T? {
let decoder = JSONDecoder()
return try? decoder.decode(type, from: data)
}
// 사용 예시
let userData = """
{"id": 1, "name": "John", "email": "john@example.com"}
""".data(using: .utf8)!
let user = decode(User.self, from: userData)
print(user?.name ?? "Unknown") // John
|
테이블뷰 셀 등록 및 재사용
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
32
33
34
| import UIKit
extension UITableView {
// 셀 등록
func register<T: UITableViewCell>(_ cellType: T.Type) {
let identifier = String(describing: cellType)
register(cellType, forCellReuseIdentifier: identifier)
}
// 셀 재사용
func dequeueReusableCell<T: UITableViewCell>(_ cellType: T.Type, for indexPath: IndexPath) -> T {
let identifier = String(describing: cellType)
guard let cell = dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier: \(identifier)")
}
return cell
}
}
// 사용 예시
class CustomTableViewCell: UITableViewCell {
// 셀 구현
}
// 뷰 컨트롤러에서
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomTableViewCell.self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(CustomTableViewCell.self, for: indexPath)
return cell
}
|
타입 스위치 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| func processValue<T>(_ value: T) {
let type = T.self
switch type {
case is String.Type:
print("문자열 타입입니다")
case is Int.Type:
print("정수 타입입니다")
case is Double.Type:
print("실수 타입입니다")
case is Person.Type:
print("Person 타입입니다")
default:
print("알 수 없는 타입입니다: \(type)")
}
}
// 사용 예시
processValue("Hello") // 문자열 타입입니다
processValue(42) // 정수 타입입니다
processValue(3.14) // 실수 타입입니다
processValue(Person(name: "John")) // Person 타입입니다
|
4. 인스턴스를 값으로 사용하기
자기 참조 반환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class Builder {
private var name: String = ""
private var age: Int = 0
func setName(_ name: String) -> Builder {
self.name = name
return self // 자기 자신을 반환하여 체이닝 가능
}
func setAge(_ age: Int) -> Builder {
self.age = age
return self
}
func build() -> Person {
return Person(name: name)
}
}
// 사용 예시 (메서드 체이닝)
let person = Builder()
.setName("Alice")
.setAge(25)
.build()
|
델리게이트 패턴에서의 활용
1
2
3
4
5
6
7
8
9
10
11
12
| protocol CustomViewDelegate: AnyObject {
func customViewDidTap(_ view: CustomView)
}
class CustomView: UIView {
weak var delegate: CustomViewDelegate?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
delegate?.customViewDidTap(self) // 자기 자신을 전달
}
}
|
5. 고급 활용 예시
의존성 주입 컨테이너
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
32
| class ServiceContainer {
private var services: [String: Any] = [:]
func register<T>(_ type: T.Type, service: T) {
let key = String(describing: type)
services[key] = service
}
func resolve<T>(_ type: T.Type) -> T? {
let key = String(describing: type)
return services[key] as? T
}
}
// 서비스 정의
protocol NetworkService {
func fetchData() -> String
}
class APIService: NetworkService {
func fetchData() -> String {
return "API Data"
}
}
// 사용 예시
let container = ServiceContainer()
container.register(NetworkService.self, service: APIService())
if let networkService = container.resolve(NetworkService.self) {
print(networkService.fetchData()) // API Data
}
|
제네릭 UserDefaults 래퍼
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
32
33
| protocol UserDefaultsStorable {
associatedtype Value
static var key: String { get }
static var defaultValue: Value { get }
}
struct UserDefaultsManager {
static func set<T: UserDefaultsStorable>(_ value: T.Value, for type: T.Type) {
UserDefaults.standard.set(value, forKey: type.key)
}
static func get<T: UserDefaultsStorable>(_ type: T.Type) -> T.Value {
return UserDefaults.standard.object(forKey: type.key) as? T.Value ?? type.defaultValue
}
}
// 키 타입 정의
struct UserNameKey: UserDefaultsStorable {
typealias Value = String
static let key = "userName"
static let defaultValue = ""
}
struct UserAgeKey: UserDefaultsStorable {
typealias Value = Int
static let key = "userAge"
static let defaultValue = 0
}
// 사용 예시
UserDefaultsManager.set("John Doe", for: UserNameKey.self)
let userName = UserDefaultsManager.get(UserNameKey.self)
print(userName) // John Doe
|
6. 주의사항과 베스트 프랙티스
타입 추론 활용
1
2
3
4
5
| // 명시적 타입 선언
let explicitType: String.Type = String.self
// 타입 추론 활용 (더 간결)
let inferredType = String.self // 컴파일러가 String.Type으로 추론
|
성능 고려사항
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 반복적인 타입 비교 시 캐싱 고려
class TypeChecker {
private static let stringType = String.self
private static let intType = Int.self
static func isString<T>(_ type: T.Type) -> Bool {
return type == stringType
}
static func isInt<T>(_ type: T.Type) -> Bool {
return type == intType
}
}
|
옵셔널 체이닝과 함께 사용
1
2
3
4
5
6
7
8
9
10
| class OptionalExample {
var value: String?
func getValue() -> String? {
return self.value // self 생략 가능하지만 명시적 표현
}
}
let example: OptionalExample? = OptionalExample()
let result = example?.getValue() // 옵셔널 체이닝
|
결론
.self
표현은 Swift에서 타입과 인스턴스를 값으로 다루기 위한 핵심 도구입니다. 메타타입을 통해 타입 정보를 런타임에 활용하고, 제네릭 프로그래밍에서 타입 안전성을 보장할 수 있습니다.
핵심 포인트:
Type.self
: 타입 자체를 메타타입 값으로 사용- 제네릭 함수에서 타입 정보 전달 시 필수
- 팩토리 패턴, 의존성 주입 등 고급 패턴에서 활용
instance.self
: 인스턴스 자체를 값으로 사용 (주로 체이닝이나 델리게이트 패턴에서)
이러한 개념을 이해하고 활용하면 더 유연하고 재사용 가능한 Swift 코드를 작성할 수 있습니다.