본문 바로가기
iOS/Swift 문법 복습

프로토콜 [Protocol]

by 황민우 2022. 6. 29.

정의

- 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진

- 프로토콜을 만족시키는 타입을 채택(conform)이라고 합니다.

- 프로토콜에 필수 구현을 추가하거나 추가적인 기능을 더하기 위해 프로토콜을 확장(extend)할 수 있습니다. 

 

 

사용

- 프로토콜을 채택해서 특정 기능을 실행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다.
- 프로토콜은 정의를 하고 제시를 할 뿐 스스로 기능을 구현하지는 않습니다. (조건만 정의)
- 하나의 타입으로 사용되기 때문에 아래와 같이 타입 사용이 허용되는 모든 곳에 프로토콜을 사용할 수 있습니다.

  • 함수, 메서드, 이니셜라이저의 파라미터 혹은 리턴 타입
  • 상수, 변수, 프로퍼티의 타입
  • 배열, 딕셔너리의 원소타입

1-1, 프로토콜 정의

protocol SomeProtocol {
    // protocol definition goes here
}

 

1-2, 프로토콜 채택

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

 


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

- 프로토콜에서는 프로퍼티가 저장된 프로퍼티인지 계산된 프로퍼티인지 명시하지 않습니다.

- 하지만 프로퍼티의 이름과 타입 그리고 gettable, settable 한지는 명시합니다.

- 필수 프로퍼티는 항상 var로 선언해야 합니다.

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

타입 프로퍼티는 static 키워드를 적어 선언합니다.

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

하나의 프로퍼티를 갖는 프로토콜을 선언합니다.

protocol FullyNamed {
    var fullName: String { get }
}

이 프로토콜을 채택하는 구조체를 선언합니다. fullName 프로퍼티는 저장된 프로퍼티로 사용될 수 있고,

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"

아래와 같이 연산 프로퍼티로 사용될 수 있습니다.

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"

메서드 요구사항 (Method Requirements)

- 프로토콜에서는 필수 인스턴스 메서드와 타입 메소드를 명시할 수 있습니다.

- 하지만 메소드 파라미터의 기본 값은 프로토콜 안에서 사용할 수 없습니다.

protocol SomeProtocol {
    static func someTypeMethod()
}

필수 메서드 지정시 함수명과 반환값을 지정할 수 있고, 구현에 사용하는 괄호는 적지 않아도 됩니다.

protocol RandomNumberGenerator {
    func random() -> Double
}

다음 코드는 따르는 프로토콜의 필수 메소드 random()을 구현한 클래스입니다.

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom a + c).truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"

이니셜라이저 요구사항 (Initializer Requirements)

- 프로토콜에서 필수로 구현해야 하는 이니셜라이저를 지정할 수 있습니다.

protocol SomeProtocol {
    init(someParameter: Int)
}

클래스에서 프로토콜 필수 이니셜라이저의 구현 (Class Implementation of Protocol Initializer Requirements)

- 프로토콜에서 특정 이니셜라이저가 필요하다고 명시했기 때문에 구현에서 해당 이니셜라이저에 required 키워드를 붙여줘야 합니다.

더보기

클래스 타입에서 final로 선언된 것에는 required를 표시하지 않아도 됩니다. final로 선언되면 서브클래싱 되지 않기 때문입니다.

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

- 특정 프로토콜의 필수 이니셜라이저를 구현하고, 수퍼클래스의 이니셜라이저를 서브클래싱하는 경우 이니셜라이저 앞에 required 키워드와 override 키워드를 적어줍니다.

protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}

 

실패 가능한 초기자 요구사항 (Failable Initializer Requirements)

- 프로토콜에서 실패가능한 이니셜라이저를 선언할 수 있습니다.


위임 (Delegation)

- 위임은 클래스 혹은 구조체 인스턴스에 특정 행위에 대한 책임을 넘길 수 있게 해주는 디자인 패턴 중 하나입니다.

protocol DiceGame {
    var dice: Dice { get }
    func play()
}
protocol DiceGameDelegate: AnyObject {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}

프로토콜 익스텐션 (Protocol Extensions)

- 익스텐션을 이용해 프로토콜을 확장할 수 있습니다.

- 아래 코드는 random()을 따르는 RandomNumberGenerator에 randomBool()을 따르도록 추가한 예입니다.

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

아래 코드와 같이 generator에서 generator.random()generator.randomBool()를 둘 다 이용할 수 있음을 확인할 수 있습니다.

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"

익스텐션을 이용해 구현을 추가할 수는 있어도 다른 프로토콜로 확장/상속할 수는 없습니다. 만약 그렇게 하고자 한다면 익스텐션이 아닌 프로토콜 자체에 구현해야 합니다.


내용 참고 출처

https://jusung.gitbook.io/the-swift-language-guide/language-guide/21-protocols#protocol-extensions

 

프로토콜 (Protocols) - The Swift Language Guide (한국어)

SnakesAndLadders는 DiceGame를 따르고 DiceGameDelegate를 따르는 델리게이트 delegate를 갖습니다. 게임을 실행(play()) 했을 때 delegate?.gameDidStart(self), delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll), delegate?.gameD

jusung.gitbook.io

 

'iOS > Swift 문법 복습' 카테고리의 다른 글

(인스턴스, 스태틱, 클래스) 메서드  (0) 2022.06.30
옵셔널 [Optional]  (0) 2022.06.28
프로퍼티 (Properties)  (0) 2022.06.27
Struct와 Class, Enum의 차이를 설명하시오.  (0) 2022.06.25

댓글