아리의 iOS 탐구생활

[Swift] Protocol 5) 타입에 대하여... 본문

Swift/문법

[Swift] Protocol 5) 타입에 대하여...

Ari Lee 2021. 8. 18. 18:21
반응형
Protocol Types

프로토콜은 first-class Citizen이다. 즉 독립적인 타입이다.

변수나 상수를 사용할때 자료형으로 사용하거나 파라미터의 자료형으로 사용할 수 있다. 반환타입으로 선언하는 것도 가능하다.

 

protocol TypeA {
    func say()
}

class A: TypeA {
    let text: String = "난 A야!"
    func say() {
        print(text)
    }
}

let a = A()

let t: TypeA = A() // 타입을 프로토콜로 선언하여도 에러가 없다.

확인해보면 프로토콜 타입으로 클래스 인스턴스를 생성하여도 에러가 없다.

왜냐하면 클래스 A는 프로토콜 TypeA를 채택하고 있기 때문이다. 이것은 슈퍼클래스 타입을 저장하는 업캐스팅과 유사하다.

업캐스팅에서는 해당 클래스에서 선언되어있는 멤버만 사용할 수 있다. 마찬가지로 프로토콜 타입으로 저장하면 프로토콜에 선언되어있는 멤버만 사용할 수 있다. 예를 들면 상수 t는 A클래스에 있는 text를 호출할 수 없다. 메서드 say()만 호출이 가능하다.

값타입은 상속이 불가능하지만 이렇게 프로토콜을 활용하면 상속과 유사한 패턴을 구현할 수 있게 된다.

 

 

 

 

🔍  프로토콜과 타입 캐스팅

Any는 모든 타입에 대응하고 AnyObject는 모든 객체에 대응한다.

Any와 AnyObject는 프로토콜이다. Swift에서 사용 가능한 모든 타입은 Any를 따르도록 설계되었고 모든 클래스들에는 AnyObject 프로토콜이 적용되어 있다.

let anyNumber: Any = 10
//anyNumber + 1 // 컴파일에러!

anyNumber에 10을 넣었다고 해서 Int형식이 아니다. Any 프로토콜을 따르는 어떤 값이기 때문이다.

 

이럴때에는 as를 이용해서 다운 캐스팅을 해야한다.

Any는 Int보다 더 큰 범위이기 때문에 작은 범위로 줄인다고 하여 다운 캐스팅이라고 한다.

 

Any는 Int 뿐만 아니라 String과 같은 전혀 다른 타입도 포함되어 있기 때문에 무조건 Int로 변환되지 않는다.

따라서 as? 를 사용해서 옵셔널을 취해야 한다.

let number: Int? = anyNumber as? Int // Optional(10)

 

옵셔널이기 때문에 옵셔널 바인딩 문법도 사용할 수 있다. 실제로 이렇게 사용하는 경우가 굉장히 많다고 한다.

if let number: Int = anyNumber as? Int {
    print(number + 5)
}

 

타입캐스팅까지는 필요없고 만약 어떤 값이 특정한 타입인지를 검사할 때에는 is 를 사용할 수 있다.

let anyNumber: Any = 10
let anyString: Any = "Ari"

print(anyNumber is Int) // true
print(anyNumber is String) // false
print(anyNumber is Any) // true
print(anyString is String) // true

 

 

 

🔍  상속과 유사한 패턴 구현

protocol TypeA {
    func say()
}

class A: TypeA {
    let text: String = "난 클래스 A야!"
    func say() {
        print(text)
    }
}

struct B: TypeA {
    func say() {
        print("난 구조체 B야!")
    }
}

struct C: TypeA {
    let c = "C"
    func say() {
        print("난 구조체 \(c)야!")
    }
}

let a = A()
let b = B()
let c = C()

//let arr = [a, b, c]
// 인스턴스의 타입이 모두 다르고 값타입과 참조타입이 섞여있기 때문에 에러가 발생한다.

let arr: [TypeA] = [a, b, c]
// 그러나 세개의 인스턴스 모두 같은 타입의 프로토콜을 채택하고 있다.
// 따라서 프로토콜 타입으로 선언해주면 모든 인스턴스를 저장할 수 있게 된다.
// 인스턴스가 값인지 객체인지는 상관하지 않고 TypeA 타입으로 캐스팅되어 저장된다.

for item in arr {
    item.say() // 이런식으로 열거도 가능하다.
    if let c = item as? C {
        print("\(c.c)는 구조체야") // 원래 형식으로 다운캐스팅하여 접근.
    }
}

 

 

 

🔍   프로토콜과 클래스의 병합

프로토콜은 다수의 프로토콜을 병합해서 하나의 임시 프로토콜을 만들 수 있다.

또한 클래스와 프로토콜을 병합할 수도 있다.

protocol TypeA {
    func say()
}

protocol TypeB {
    func sayNumber()
}

class A: TypeA, TypeB {
    let text: String = "난 클래스 A야!"
    func say() {
        print(text)
    }
    func sayNumber() {
        print("2")
    }
}

struct B: TypeA {
    let b = "B"
    func say() {
        print("난 구조체 \(b)야!")
    }
}

class C: A {

}

var ab: TypeA & TypeB = A() // 프로토콜 2개를 병합하여 자료형으로 선언
//ab = C() // 구조체 C는 프로토콜 TypeA만 채택하고 있기 때문에 저장할 수 없다.

var ac: A & TypeA = A()
ac = C() // A 클래스를 상속하기 때문에 저장할 수 있다.

 

 

 

🔍  Optional Protocol Requirements

프로토콜에서 옵셔널을 사용하려면 반드시 프로토콜 앞에 @objc를 붙여줘야 한다. 그리고 멤버앞에는 @objc 키워드와 optional 키워드 모두 붙여줘야 한다. 

@objc protocol name {
    @objc optional var A: Int { get set }
}

여기서 @objc는 스위프트 코드를 Objective-C에서 사용할 수 있게 해준다. 그리고 optional은 선택적 멤버를 선언할 때 사용된다.

@objc를 사용하게 되면 자동으로 AnyObject 프로토콜이 상속된다. 그래서 클래스에서만 채택할 수 있게 된다.

Objective-C에서는 구조체와 열거형에 프로토콜을 허용하지 않기 때문에 필요한 제한이다.

 

 

 

 

 @objc protocol optionalA {
    @objc optional var A: Int { get set }
    @objc optional var B: String { get set }
    func C()
    @objc optional func D()
}

class Test: optionalA {
    func C() {
        print("옵셔널!")
    }
}

let test: optionalA = Test()
test.C() // "옵셔널!" 출력
test.A // nil
test.B // nil
test.D?() // nil

여기에서 nil은 속성에 nil이 저장되어 있다는 뜻이기도 하지만 해당 속성이 구현되어 있지 않다는 뜻이기도 하다. 

구현되어있지 않은 메서드를 호출할때는 옵셔널 체이닝 이용하여 호출할 있다.

 

 

 

 

✔️  Reference

 

Protocols — The Swift Programming Language (Swift 5.5)

Protocols A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of tho

docs.swift.org

 

반응형
Comments