프로토콜이란?

프로토콜은 클래스, 구조체 그리고 열거형에 적용시킬 수 있는 무엇입니다. 무엇은 뭘까요?

일단 프로토콜은 클래스 구조체 열거형과같은 타입은 아닙니다!.

프로토콜의 역할은 타입내의 메서드, 프로퍼티, 그리고 다른 요소들이 특정 기능에 맞도록 설계되도록 청사진을 정의해줍니다.

프로토콜의 요구사항을 만족시키며, 즉 프로토콜을 적용시켜 정의된 타입은 ‘해당 프로토콜을 따르게됩니다.’라고 표현하는게 프로토콜의 역할을 이해하기 좀 더 도움이 되실겁니다.

프로토콜의 요구사항을 충족시킨 타입들은 프로토콜에 정의된 특정 기능들이 적용됩니다.



프로토콜 적용

프로토콜은 클래스, 구조체, 열거형과 비슷한 형식으로 정의합니다.

protocol SomeProtocol {
    // protocol definition goes here
}


직접 정의했거나 혹은 기존의 프로토콜을 타입에 적용시키고싶으면

타입을 정의할때 타입의 이름 뒤에 적용시키고자하는 프로토콜명을 써주면 적용시킬 수 있습니다.

여러개의 프로토콜을 적용시키려면 ,(쉼표)로 구분하여 나열해줍니다.

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}


클래스일 경우 부모클래스를 상속받는 경우에도 마찬가지로 프로토콜과 같이 클래스명 뒤에 명시해줍니다.

상속도받고 프로토콜도 따를시에는 상속받는 클래스를 먼저 명시해주고 그뒤에 ,(쉼표)로 구분하여 쭉 나열해주면 됩니다!

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}



프로토콜 프로퍼티

프로토콜내에서도 프로퍼티를 정의해줄 수 있습니다. 인스턴스 프로퍼티, 타입 프로퍼티 모두!

프로토콜내에서 정의되는 프로퍼티는 해당 프로퍼티가 정확하게 저장프로퍼티인지 연산프로퍼티인지 규정짓지 않습니다.

다만 프로퍼티의 이름 그리고 타입을 명시해주며

각 프로퍼티에 접근자, 설정자를 활용하여 { get } 혹은 { get set }을 명시해줘야합니다.

만약 프로퍼티에서 정의된 프로퍼티가 get(접근자)set(설정자) 모두를 가지고 있다면

해당 프로퍼티는 포로토콜을 따르는 타입내에서 사용될때 오로지 저장프로퍼티나 오로지 읽기전용 연산프로퍼티로 사용되어질 수 없습니다.

둘 중 하나만 선택적으로 사용할 수 없고 정의된 바와 같이 get, set의 기능을 모두 수행해야 한다는 뜻입니다.

하지만 !!

get만 명시되어 있을 경우는 어떤 타입의 프로퍼티(저장프로퍼티, 연산프로퍼티, 연산저장프로퍼티)로든 사용할 수 있습니다.

또한 프로토콜 프로퍼티는 var로 정의되어야만 합니다.

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}


프로토콜에서 타입프로퍼티를 정의할때는 static만을 사용해줍니다.
(클래스사용할때는 class, static 모두 사용가능!)

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

조금 복잡한 코드를 예시로 들어보겠습니다.

protocol FullyNamed {
    var fullName: String { get }
}

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + " " : "") + name
    }
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"

위의 코드는 FullyNamed 프로토콜을 따르는 Starship 클래스를 정의하고있습니다.

FullyNamed 포로토콜의 fullName 프로퍼티는 저장프로퍼티로 프로토콜에서 정의되어있지만

Starship 클래스에서는 해당 프로퍼티를 이를 읽기전용 연산프로퍼티로 사용하고있습니다.



프로토콜 메서드

프로토콜에서 메서드는 일반적인 메서드와 동일하게 인스턴스메서드 그리고 타입메서드 모두 정의가 가능합니다.

또한 그 정의방식도 일반 인스턴스메서드, 타입메서드와 동일합니다

다만!!

메소드의 정의만 해줍니다. 중괄호를 이용해 내부에 실제 메서드의 기능을 서술하지 않습니다.
(메서드 내부 기능은 실제 프로토콜을 따르는 타입내에서 정의!)

또한 일반적인 메서드와같이 가변인자를 활용하여 메서드에서 사용할 파라미터를 정의해주지만

파라미터에 기본값을 지정하여 사용할 수는 없습니다.

protocol Bobby {
    static func firstMethod() -> String 
}

class Babywalnut: Bobby {
    static func firstMethod() -> String {
        return "firstMethod"
    }
}


타입 메서드는 타입 프로퍼티와 마찬가지로 static을 사용하여 정의가되지만

실제로 해당 프로토콜을 타입중 클래스에 적용시키고 타입메서드를 사용할때는 static과 class로 모두 사용할 수 있습니다.

즉, 다시말하면!

protocol Bobby {
    static func firstMethod() -> String

    static func secondMethod()
}

라고 정의되어있을때 이 프로토콜을 클래스에 적용시켜보면

class Babywalnut: Bobby {
    static func firstMethod() -> String {
        //code
    }
    
    class func secondMethod() {
        //cde
    }
}

와 같이 프로토콜에는 타입메서드가 static으로 정의되어 있어도 프로토콜을 클래스에 적용시킬때는

static, class로 모두 구현할 수 있습니다.


프로토콜 프로퍼티와 메서드에 대해 정리하자면!

* 프로토콜 프로퍼티는 var로 정의되어야합니다
* 프로토콜 프로퍼티는 정의 자체로 저장프로퍼티, 읽기전용 연산프로퍼티임을 규정짓지 않습니다
* 프로토콜 프로퍼티가 { get }으로 정의되어 있으면 포로토콜을 따르는 타입내에서 어떤종류의 프로퍼티로도 사용가능합니다
* { get set }으로 정의되어 있다면 프로토콜을 따르는 타입내에서 두 기능 모두 수행하는 프로퍼티로 사용되어야합니다.
* 프로토콜의 타입프로퍼티는 항상 static으로 정의합지만 클래스에서 사용될때는 static,class로 모두 구현이 가능합니다.
* 프로토콜에서 인스턴스메서드와 타입메서드 모두 정의가능합니다.
* 프로토콜에서는 메서드를 정의만 해주며 메서드의 내부내용은 타입에 적용시켜줄 때 정의시켜줍니다.
* 프로토콜 메서드의 파라미터는 가변인자를 사용하고 파라미터에 기본값을 할당할 수 없습니다.
* 프로토콜의 타입메서드는 항상 static을 사용해 정의하지만 클래스에서 사용될때는 static,class로 모두 구현이 가능합니다.