Featured image of post Protocols

Protocols

Protocols

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements.

특정 작업이나 기능에 적합한 메소드, 프로퍼티의 청사진
구조체, 클래스, 열거형에서 프로토콜을 채택(Adopted)하여 사용가능
추가 기능을 구현하기 위한 protocol extension도 사용할 수 있다.

Protocol Sysntax

1
2
3
protocol ProtocolName {
    // protocol definition goes here
}

여러개의 프로토콜을 채택할 수도 있다.

1
2
3
struct SomeStructure: ProtocolName, AnotherProtocol {
    // protocol definition goes here
}

클래스가 다른 클래스를 상속하는 경우에는 부모 클래스를 먼저 적고, 그 다음에 프로토콜을 적는다.

1
2
3
class SomeClass: Superclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

Property Requirements

프로토콜에서는 프로퍼티가 저장 프로퍼티인지 연산 프로퍼티인지 명시하지 않고 이름과 타입 그리고 gettable, settable한지 명시한다.

프로퍼티는 항상 var로 선언해주어야 한다.

1
2
3
4
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

static접두사를 붙여서 타입 프로퍼티로 선언할 수 있다.

1
2
3
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

단일 인스턴스 Property 예제

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// name이라는 프로퍼티를 가지고 있는 프로토콜 구현, 읽기만 가능해도 상관없음(읽기, 쓰기 가능해도 됨)
protocol FullyNamed {
    var fullName: String { get }
}

// Person 구조체는 FullyNamed 프로토콜을 채택
struct Person: FullyNamed {
    var fullName: String
}

let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"

// FullyNamed 프로토콜을 채택한 조금 더 복잡한 Starship 클래스
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

프로토콜을 준수하기 위해 특정 메소드 혹은 타입 메소드를 요구할 수 있다.
프로토콜은 구현을 하지 않는 요구사항이므로 { } 코드 클로저를 사용하지 않고 함수 이름과 매개변수, 반환 타입만 명시한다.
타입 메소드의 경우 static 접두사를 붙여서 명시한다.

Syntax

1
2
3
protocol SomeProtocol {
    static func someTypeMethod()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// RandomNumberGenerator 프로토콜 구현
protocol RandomNumberGenerator {
    func random() -> Double
}

// LinearCongruentialGenerator 구조체는 RandomNumberGenerator 프로토콜을 채택
// 내부에 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"

Mutating Method Requirements

값 타입(구조체, 열거형)에서 메소드를 구현할 때는 메소드 앞에 mutating 키워드를 사용해야 한다.

Initializer Requirements

프로토콜 구현 시, 특정 이니셜라이저를 요구할 수 있다.
메소드와 마찬가지로 구현부는 작성하지 않는다

1
2
3
protocol SomeProtocol {
    init(someParameter: Int)
}

Class Implementations of Protocol Initializer Requirements 프로토콜을 채택한 Class의 이니셜라이저 구현

클래스에서 프로토콜을 채택한 경우, 이니셜라이저를 구현할 때는 required 키워드를 사용해야 한다.

1
2
3
4
5
lass SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

SomeClass를 상속받는 클래스에서도 해당 프로토콜을 준수해야 하기 때문이다. 따라서 SomeClass가 상속받을 곳이 없는 최종 클래스라면 required 키워드를 사용하지 않아도 된다.

자식 클래스가 부모 클래스의 지정된 초기화를 재정의하고 프로토콜의 이니셜라이저도 구현해야하는 경우 required override init을 사용한다. 😅

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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 실패할 수 있는 초기화 요구

프로토콜에서도 init?을 사용하여 Failable Initializer를 구현할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
protocol SomeProtocol {
    init?()
}

// 프로토콜이 failable initializer를 요구해도 init()으로 구현해도 된다
class Some: SomeClass {
  init() {
  }

//하지만 프로토콜이 init()을 요구하면 init?()으로 구현할 수 없다

Protocols as Types

프로토콜은 실제 기능을 구현하지는 않지만 하나의 독립적인 타입으로 사용할 수 있는 일급시민이다.

따라서 아래의 3가지가 가능하다

  • 함수 또는 이니셜라이저의 매개변수 Type or Return Type
  • 상수, 변수 프로퍼티의 Type
  • Array, Dictionary 또는 기타 컨테이너 항목 Type
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Dice {
    let sides: Int
    // RandomNumberGenerator 프로토콜을 타입으로 사용
    let generator: RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

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

Reference

Apple Docs - Protocols