[Swift] 프로토콜 - 1 ( Protocol )

프로토콜은 특정 작업이나 기능에 적합한 메서드, 프로퍼티 및 기타 요구사항에 대한 청사진을 제공한다

프로토콜은 클래스, 구조체, 열거형에 의해 채택되며 프로토콜을 채택한 타입들은 실제 구현을 할 수 있다

프로토콜의 요건을 충족하는 모든 타입은 해당 프로토콜을 준수한다고 표현한다

한마디로 프로토콜은 어떤 기능 작업을 하기 위해 구현해야할 혹은 요구하는 프로퍼티나 메서드의 집합이랄까? 만약 그림을 그린다고 하면 그림을 그리기 위해 펜, 붓등 “도구”를 사용해야 하고 삼각형, 원등 어떤 “형태”를 그려야 하겠지? 그럼 그림을 그리는 “기능”을 프로토콜로 정의하면 도구와 형태를 청사진으로 제공하고 실제 준수하는 타입들은 도구는 펜을 사용, 형태는 원을 그리는 것 처럼 구체적인 구현을 해야함

요구사항을 지정하는 것 이외에도 프로토콜을 확장(extension)하여 요구사항 중 일부를 구현하고 추가 기능을 구현하는 것 또한 가능하다

문법(Syntax)

protocol SomeProtocol { }
class SomeClass: SomeProtocol { }
struct SomeStruct: SomeProtocol { }
enum SomeEnum: SomeProtocol { }

정의는 protocol 키워드를 사용하여 정의하고, 프로토콜을 채택할 때는 상속 처럼 : 뒤에 프로토콜 명을 명시

클래스의 경우, 상속을 받게 되면 super class를 제일 처음에 명시하고 그 뒤에 프로토콜을 기입

protocol SomeProtocol { }
class SuperClass{ }
class SubClass: SuperClass, SomeProtocol { }

프로퍼티 요구사항(Property Requirement)

프로토콜은 특정 이름과 타입을 가지는 인스턴스 / 타입 프로퍼티를 요구할 수 있음

저장 / 연산프로퍼티인지 지정하지 않고 프로퍼티의 이름과 타입만을 지정

추가로 각 프로퍼티가 getable만 가능한지 getable / setable 인지 명시해야 함

만약 프로토콜에서 요구하는 프로퍼티가 값을 가져오고, 새로운 값으로 설정도 가능해야 하는 경우

상수 프로퍼티 또는 getable만 가능한 프로퍼티는 이런 요구사항에 충족하지 않음

요구하는 프로퍼티가 getable만 요구한다면 연산 / 저장 프로퍼티 모두 충족이 가능하다

프로토콜에 명시된 프로퍼티의 요구사항은 최소 만족 조건이다

만일 프로토콜이 getable을 요구한다면 프로토콜을 준수하는 타입에서는 getable / setable을 지원하는 프로퍼티로 사용해도 프로토콜이 요구하는 getable을 만족하기 때문에 가능하다

만일 getable / setable을 요구하였는데 getable만 지원하는 프로퍼티로 구현하면 이는 컴파일 에러를 발생 시킴

프로퍼티 요구사항은 항상 변수 프로퍼티로 선언

getable 은 get 키워드로, setable은 set 키워드로 명시한다

protocol SomeProtocol {
    var name: String { get }
    var age: Int { get set }
    static var typeProperty { get } 
}

name은 최소 읽기 전용 프로퍼티로 ( 물론 set 도 가능 하다 )

age는 읽고 쓸 수 있는 프로퍼티로 구현해야 한다

타입 프로퍼티는 동일하게 static 키워드로 사용 가능하다.

타입 프로퍼티가 요구되는 프로토콜을 클래스 타입이 채택할 경우 실제 구현에서는 class / static 두 키워드 모두 사용이 가능하다 최소 요구사항이기 때문

protocol FullyNamed {
    var fullName: String { get }
}

fullName 프로퍼티만을 가지는 FullyNamed 프로토콜

struct Human: FullyNamed {
    var fullName: String
}

let john = Human(fullName: "john")

위의 경우에는 프로토콜의 프로퍼티를 저장 프로퍼티로 사용

struct Human: FullyNamed {
    var firstName: String
    var lastName: String
    var fullName: String { "\(lastName) \(firstName)" }
}

let john = Human(firstName: "sweet", lastName: "dev")

이 경우에는 연산 프로퍼티로 사용

메서드 요구사항( Method Requirement)

타입 / 인스턴스 메서드를 요구할 수 있다

일반적인 타입 / 인스턴스 메서드처럼 정의하지만 중괄호와 본문은 생략한다

청사진일 뿐이니까!

메서드를 정의할 때 기본값 지정은 불가하지만 그외 가변 매개변수, inout 매개변수는 사용 가능하다

프로토콜에서 static으로 선언된 메소드일지라도 실제 구현하는 클래스에서는 class 키워드로 변경이 가능하다

protocol SomeProtocol {
    // 메서드 본문 생략
    static func printHello()
}
class SomeClass: SomeProtocol {
    // static -> class 변경 가능
    class func printHello() { print("hello") }
}
protocol RandomGenerator {
    func random() -> Double
}

RandomGenerator 프로토콜은 Double을 반환하는 random() 메서드를 구현하라고 요구하고 있다

어떤식으로 난수를 발생시켜야하는지 가이드라인 같은건 존재하지 않는다

실제로 이 프로토콜을 채택하는 타입에서 어떤식으로든 Double을 반환하게 구현하면 된다.

변이 메서드 요구사항 ( Mutating Method Requirement )

메서드가 속한 인스턴스를 수정해야 하는 경우가 있다

프로토콜을 사용하는 타입이 인스턴스를 수정할 수 있도록 요구하려면

mutating 키워드를 사용하여 정의하면 된다

mutating 키워드로 메서드를 요구하는 프로토콜을 준수하는 타입이 클래스라면 mutating 키워드는 생략할 수 있다

protocol Togglable {
    mutating func toggle()
}

mutating 키워드를 보면 인스턴스를 수정할 수 있는 메서드임을 예상할 수 있음

enum OnOffSwitch: Toggleable {
    case on, off

    mutating func toggle() {
        switch self {
        case .on:
            self = .off
        case .off:
            self = .on
        }
    }
}

var swc = OnOffSwitch.on
print(swc) // on
swc.toggle()
print(swc) // off

이니셜라이저 요구사항

실패 가능한 이니셜라이저를 포함해 이니셜라이저를 요구할 수 있다

protocol SomeProtocol {
    var name: String { get set }

    init?()
    init(name: String)
}

프로토콜 이니셜라이저 요구사항의 클래스 구현

프로토콜의 이니셜라이저를 클래스에서 편의 / 지정 이니셜라이저 모두 구현 가능하다

다만 편의 / 지정 이니셜라이저 두 경우 모두 프로토콜에서 요구하는 이니셜라이저이기 때문에

required 키워드를 붙여야 한다

final 키워드가 사용되었다면 required 키워드는 생략함 어차피 서브클래싱이 안되기 때문

class SomeClass: SomeProtocol {
    var name: String
    required convenience init?() { }
    required init(name: String) { }
}

만약 슈퍼클래스의 지정이니셜라이저를 재정의하고 프로토콜에서 요구하는 이니셜라이저가 재정의하는 이니셜라이저와 일치한다면 required override를 모두 작성한다

protocol SomeProtocol {
    init()
}
class SuperClass { 
    init(){ }
}

class SubClass: SuperClass, SomeProtocol {
    required override init() { }
}

태그:

카테고리:

업데이트:

댓글남기기