[Swift] Protocol Expert - 3

Protocols and the type system

좋은 시스템의 지원을 받을 때 프로토콜은 그 진가가 나타나며 Swift가 그 예이다.

이번 포스트에서는 프로토콜 타입이 무엇인지, 프로토콜 타입을 효과적으로 사용하는 법에 대해

학습한다.

Existentials

이전에 변수의 타입을 프로토콜로 정의한 적이 있다

let greeter: Greetable = GermanGreeter()

항상 변수를 정의하는 방식이기에 새롭지 않게 느낄수 있다.

하지만 Int, String같은 일반적인 타입을 사용하는 것과 프로토콜을 타입으로 사용하는 것에는

큰 차이가 존재한다.

greeter: Greetable은 일반적인 타입으로 보이겠지만

프로토콜 타입은 existential 타입이라고 하는 특수한 타입이다.

existential이라는 이름이 화려해보이고 고급져 보이지만 그다지 복잡한 개념은 아니다

exsitential 타입을 간단하게 설명하자면 구체적인 타입에 대한 자리표시자(placeholder)로 생각할 수 있다.

컴파일러는 exsitential 타입에 대해 단지 특정 프로토콜을 준수하는 타입이라고 해석하고 이해한다.

위 코드를 예로들면 greeterGreetable을 준수하는 타입의 변수이며 Greetable을 준수하는 타입이라면

GermanGreeter, EnglishGreeter, KoreanGreeter등 어떤 타입이라도 할당할 수 있다.

existential 타입은 변수 뿐만 아니라 메소드의 파라미터, 배열의 Element, 기타 data 구조체에 프로토콜을

타입으로 사용할 수 있도록 한다.

프로토콜을 타입으로 사용 (Using protocols as types)

다음은 프로토콜을 타입으로 사용하는 예이다.

func greet(with greeter: Greetable){ ... }
let englishGreeter: Greetable = EnglishGreeter()
let allGreeter:[Greetable] = [germanGreeter, englishGreeter]

프로토콜을 타입으로 사용할 때 유용한 방법들이 많이 존재한다.

예를 들어 & 연산자를 사용해 여러 타입을 단일 타입으로 취급하도록 구성할 수 있다.

func localizedGreet(with greeter: Greetable & Localizable)

greeter 파라미터는 프로토콜 GreetableLocalizable을 모두 준수하는 타입이어야 전달 받을 수 있다

지금 예시는 프로토콜 타입 & 프로토콜 타입이지만

구조체 타입 & 프로토콜 타입(Date & Codable)

클래스 타입 & 프로토콜 타입(UITableviewCell & Selectable)의 구성도 가능하다

2개의 타입을 단일 타입으로 취급하였지만, 그 이상의 타입들로도 구성할 수 있다.

이런식으로 구성하는 타입(non-nominal types라고도 함)은 위 파라미터 변수와 같이 변수의 타입으로만 사용이 가능하다

예를 들어

extension UIViewController: Greetable & Localizable

이런식으로 사용할 수는 없다.

하지만 다른방식으로 비슷한 효과를 낼 수 있다.

프로토콜을 준수하는 클래스와 그클래스의 서브클래스를 대상으로하는 확장을 정의할 수 있다

extension UITableviewDelegate where Self: UIViewController {
    func showAlertForSelectedCell(at index: UIIndexPath) {}
}

이런식으로 확장을 정의하면 UITableViewDelegate 프로토콜을 준수하는 UIViewController

그 하위클래스에 대해서만 자동으로 showAlertForSelectedCell(at:) 메소드를 사용할 수 있도록 확장을 한다.

즉 어떤 타입이 UITableviewDelegate & UIViewController을 만족하는 경우 인셈이다

let hasTableViewViewController: UITableviewDelegate & UIViewController = ...()
let generalViewController: UIViewController = ...()
hasTableViewViewController.showAlertForSelectedCell(at: someIndexPath)
generalViewController.showAlertForSelectedCell(at: someIndexPath)

이 코드에서 generalViewControllershowAlertForSelectedCell(at:)메서드를

호출하는 라인은 컴파일러가 에러를 노출시킬 것이다.

또한 제네릭 파라미터가 특정 프로토콜을 준수할때만 제네릭 타입을 확장할 수 있도록 정의할 수 있다.

extension Array where Element: Greetable {
    var allGreeting: String { self.map { $0.greet().joined }
}

이 코드는 Array제네릭 파라미터 ElementGreetable 프로토콜을 준수하는 타입일 때

Array를 확장하여 allGreeting 연산프로퍼티를 사용할 수 있도록 한다

위 사례와 유사하게 제네릭 파라미터가 특정 프로토콜을 준수할 때만 제네릭 타입에 프로토콜을 추가하도록 할 수 있다.

extension Array: Localizable where Elemnt: Localizable {
    static var supportedLanguages: [Language] {
        Element.supportedLanguages
    }	
}

Array의 제네릭 파라미터 Element의 타입이 Localizable을 준수하는 타입일 때

Array에 대해 Localizable 프로토콜을 추가하도록 확장하는 코드이다.

실제 Array의 extension을 살펴보면
extension Array: Equatable where Element: Equatable
같은 코드를   있다.
이는 Element가 Equtable을 준수하는 타입일  Array 또한 Equatable 프로토콜을 채택하여 
Equatable의 요구사항을 제공한다

합성된 프로토콜 적합성(Synthesized protocol conformance)

Swift를 사용하다보면 Codable 같은 몇몇 프로토콜을 채택하면 구현을 하지 않아도

프로토콜의 기능을 사용할 수 있는 상황이 있을것이다.

이런 상황은 스위프트가 프로토콜 구현을 합성하여 생성하기 때문에 발생한다.

Swift는 Equatable, Hashable, Comparable 그리고 Codable의 종류인

Encodable, Encodable에 대해 이러한 합성된 프로토콜 구현을 수행한다.

위의 프로토콜 리스트중 Hashable, Equatable 같은 몇몇 프로토콜은 
클래스 타입에서는 합성된 프로토콜 구현을 수행하지 않기 떄문에 직접 요구사항에 대해 
구현을 해줘야한다.

유의할 점으로 프로퍼티들의 타입이 해당 프로토콜을 준수해야 Synthesized protocol conformance을 제공 받을 수 있는 제한사항이 있다.

댓글남기기