Post

.self란

.self란

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 코드를 작성할 수 있습니다.

This post is licensed under CC BY 4.0 by the author.