프로토콜(Protocol)

ellyheetov·2021년 2월 28일
0
post-thumbnail

Protocol이란?

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

프로토콜이란 특정 기능 수행에 적합한 메소드, 프로퍼티 등을 정의한 것이다. 작성된 프로토콜은 class나 struct, enum을 통해 구현된다. 프로토콜을 구현한 다는 것은 프로토콜에 선언된 명세에 따라 실질적으로 프로퍼티에 값을 할당하여 선언하거나 메소드의 내용을 작성하는 것을 말한다.

Protocol property

프로토콜에 선언되는 프로퍼티는 초기값을 할당 할 수 없다. 때문에 var키워드를 이용하여 선언되고 get과 set이 가능하다.

protocol SomeProtocol {
    var mustBeSettable: Int { get set } // 인스턴스 프로퍼티
    var doesNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set } // 타입 프로퍼티
}

Protocol method

프로토콜은 특정한 인스턴스 메소드 또는 타입 메소드를 가질 수 있다. 프로토콜의 메소드는 메소드 선언 뒤에 중괄호 블록을 하지 않는다는 특징이 있다.

protocol SomeProtocol {
    static func someTypeMethod() // 타입 메소드
}
protocol RandomNumberGenerator {
    func random() -> Double // 인스턴스 메소드
}

Mutating Method

구조체 내의 메소드가 프로퍼티를 변경하는 경우 메소드 앞에 반드시 mutating을 붙여야 한다.

protocol Togglable {
    mutating func toggle()
}
enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch is now equal to .on

mutating이 붙어있다면, 내부 프로퍼티의 값이 변경된다는 것을 예측할 수 있다. 반대로 mutating이 붙어있지 않다면 내부 프로퍼티의 값이 변경되지 않는 다는 것을 예상할 수 있다. 또한, 클래스를 대상으로 간주하고 작성된 프로토콜일때에도 mutating을 적지 않는다.

Initializer

프로토콜에서도 초기화 메소드를 정의 할 수 있다.

일반적인 초기와 문과 같은 방식이다. 단, 내부 초기화 로직은 작성하지 않고, 단순히 이름과 매개변수 타입만 작성하면된다.

protocol SomeProtocol {
    init(someParameter: Int)
}

초기화 구문이 있는 프로토콜을 채택한 경우

프로토콜에서 특정 초기화 구문이 필요하다고 명시하였으므로 구현에서 해당 초기화에 required 키워드를 붙여서 초기화 한다. (클래스에만 해당 함, 구조체에서는 required 키워드를 사용하지 않는다)

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

참고!

required 키워드가 붙어있다면, 모든 하위 클래스는 반드시 초기화를 구현해야 한다. 하지만 하위 클래스에서 required initializer를 overriding하는 경우에도 override 키워드를 적지 않아도 된다.

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}
class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

하지만, 만약의 하위 클래스에서 상위 클래스의 initializer를 override함과 동시에 프로토콜의 initializer를 구현해야 한다면 requiredoverride를 둘 다 적어 주어야 한다.

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
    }
}

타입으로서의 Protocol

프로토콜은 자료형으로서도 사용이 가능하다. 때문에, 타입을 사용하는 모든 경우에 프로토콜을 사용할 수 있다.

  • 함수, 메소드의 파라미터 또는 return 타입
  • 상수, 변수, 프로퍼티의 타입
  • 컨테이너인 배열, Dictionary 등의 아이템 타입

프로토콜은 타입이기 때문에 SomeProtocol와 같이 첫 글자를 대문자로 적어야 한다.

class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

위의 코드는 RandomNumberGenerator 프로토콜을 generator의 타입으로 사용하고 있으며, init 메소드의 파라미터로 사용하고 있다.

Dice를 초기화 하기 위해 generator 파라미터에 RandomNumberGenerator 프로토콜을 따르는 인스턴스를 넣어주면 된다.

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
    print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4

Delegation

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)
}

DiceGame 프로토콜을 선언하고 DiceGameDelegate에 실제 DiceGame의 행위와 관련된 구현을 위임한다.

델리게이션에 대한 설명은 다음 글로 대체한다.

프로토콜 타입의 Collections

프로토콜은 타입이므로 array와 dictionary와 같이 사용할 수 있다.

let things: [TextRepresentable] = [game, d12, simonTheHamster]

iterate도 가능하다.

for thind in thins {
    print()
}

프로토콜 상속

프로토콜은 상속을 통해 정의된 프로퍼티나 메소드, 초기화 블록의 선언을 다른 프로토콜에 물려줄 수 있다. 프로토콜은 클래스와 다르게 다중 상속이 가능하다.

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

클래스 전용 프로토콜

프로토콜은 클래스, 구조체, 열거형까지 광범위한 객체들이 구현할 수 있지만, 때로는 클래스만 구현할 수 있도록 제한된 프로토콜을 정의해야할 때가 있다. 이를 클래스 전용 프로토콜이라고 한다. 프로토콜을 정의할 때 AnyObject 사용하여 클래스 전용 프로토콜임을 컴파일러에게 알려줄 수 있다.

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

프로토콜 합성

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"

Named & Aged를 사용하면 두개의 프로토콜을 동시에 따르는 타입을 정의할 수 있다.
wishHappyBirthday 함수의 celebrator는 Named와 Aged 프로토콜을 동시에 따르고 있다.

Checking for Protocol Conformance

어떤 타입이 특정 프로토콜을 따르고 있는지 확인 할 수 있다.

  • is : 인스턴스가 특정 프로토콜을 따르고 있는지 확인한다. 따르고 있는 경우 true를 반환한다.
  • as? : 특정 프로트콜 타입을 따르는 경우 Optional 타입의 프로토콜로 다운 캐스트 하게되고, 따르지 않는 경우 nil을 반환한다.
  • as! : 강제로 특정 프로토콜을 따르도록 정의한다. 만약 다운 캐스팅에 실패할 경우 에러를 발생시킨다.

선택적 옵셔널 요구조건(Optional Protocol Requirements)

프로토콜을 선언하면서 필수 구현이 아닌 선택적으로 구현으로 남겨둘 수 있다. @object 키워드를 프로토콜 앞에 붙이고, 개별 함수 혹은 프로퍼티에는 @objectoptional키워드를 붙인다. 단, @object프로토콜은 클래스 타입에서만 채택될 수 있고 구조체나 열거형에서는 사용할 수 없다.

@object optional 키워드가 붙은 메소드들은 optional로 wrapping이된다.

 ((Int) -> String)?

optional객체를 반환하므로 optional chaning도 적용이 가능하다.

@objc protocol CounterDataSource {
    @objc optional func increament(forCount count: Int) -> Int
    @objc optional var fixedIncrement : Int { get }
}
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

dataSource는 increment나 fixedIncrement를 구현하지 않았을 수도 있으므로 optional chaning을 통해 확인한다.

참고
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID268

profile
 iOS Developer 좋아하는 것만 해도 부족한 시간

0개의 댓글