프로토콜(protocol)
- 어떤 태스크나 기능을 수행하는데 적합한 메소드, 프로퍼티, 기타 요구사항에 대한 청사진을 정의한다.
- 클래스, 구조체, 열거형이 이런 요구사항을 구현할지 여부를 결정할 수 있다.
- 프로토콜의 요구사항을 만족시키는 타입을 해당 프로토콜을 승인(confirm)했다고 말한다.
- 프로토콜의 요구사항 만족시키고 나서 추가 기능을 구현할 수도 있다.
정의
사용용도
문법
- class, structure, enum과 유사
protocol SomeProtocol{ //프로토콜 정의 들어가는 곳 }
struct SomeStructure: FirstProtocol, AnotherProtocol{
//struct의 정의 들어가는 곳
}
- SomeStructure가 프로토콜을 채택하는 경우
:뒤에 채택한 프로토콜을 추가한다. 복수개의 프로토콜을 추가할 수 있으며 이런 경우,로 여러 프로토콜을 추가할 수 있다.
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
//class의 정의 들어가는 곳
}
프로퍼티 요구사항
- 프로토콜에서는 프로퍼티 이름과 타입만 지정한다.
각 프로퍼티가 get이나 get/set 가능한지를 지정해야한다.
- get/set 가능한 프로퍼티
- 값저장형과 read-only 프로퍼티는 불가
- get
- 어떤 형태의 프로퍼티도 가능
- 구현부에서 set가능한 프로퍼티로도 가능
protocol SomeProtocol{ var mustBeSettable: Int{get set} var doesNotNeedToBeSettable: Int {get} }
- get/set 가능한 프로퍼티
staic 키워드를 가지는 프로퍼티
protocol AnotherProtocol { static var someTypeProperty: Int {get set} }단일 인스턴스(instance) 프로퍼티를 가지는 경우
protocol FullyNamed {
var fullName: String { get }
}
- 그냥 fullName이라는 프로퍼티만 가지면 되는 경우
struct Person: FullyNamed {
var fullName: String
}
let jeyong = Person(fullName: "Jeyong Shin")
- Person 구조체는 FullNamed 프로토콜을 채택했다. 따라서 fullName이라는 프로퍼티를 가지는 부분을 구현해야한다.
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")
- 위 예제에서는 계산형 읽기전용 프로퍼티로 fullName을 구현했다. 모든 Starship 인스턴스는 name과 prefix 프로퍼티를 갖는다. prefix는 선택적으로 값이 있을 수도 없을 수도 있다.
메소드 요구사항
- 프로토콜은 특정 인스턴스 메소드와 타입 메소드를 정의할 수 있다. 메소드 정의만 필요하므로
{ }와 같이 바디를 구현하는 부분은 생략한다. - 가변길이 인자도 허용한다.
- 인자의 디폴트 값을 지정하는 것은 허용하지 않는다.
protocol SomeProtocol {
static func someTypeMethod()
}
- 단일 인스턴스 메소드를 가지는 프로토콜
protocol RandomNumberGenerator { func random() -> Double } - 위 예제에서 RandomNumberGenerator는 random이라는 Double형 값을 반환하는 메소드를 정의한다.
- RandomNumberGenerator는 값을 어떻게 만드는지 가정하고 있지 않다. 실제로 이 프로토콜을 받는 클래스나 구조체가 생성하는 방법을 정한다.
class LinearCongruentialGenerator:
RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 38.77.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
print("And another one: \(generator.random())")
변경가능한 메소드 요구사항
- 메소드가 인스턴스를 수정하는 경우
- value 타입(structure, enum)에 있는 인스턴스 메소드의 경우 func 키워드 앞에
mutating키워드를 붙인다. 이렇게 하면 해당 메소드는 인스턴스 프로퍼티를 수정하는 것을 허용한다는 뜻이다. - 주의 : mutating은 오직 value 타입인 struct와 enum에서만 사용
- 즉 이 메소드를 호출하면 인스턴스의 상태를 변경할 수 있다는 것을 표현하기 위함이다.
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()
초기화 요구사항
- 특정 초기화 구현시키기 위한 프로토콜
protocol SomeProtocol {
init(someParameter: Int)
}
required키워드를 통해 프로토콜의 초기화 init을 제대로 지켰다는 것을 표시하기 위해서다class SomeClass: SomeProtocol { required init(someParameter: Int) { //초기화 구현 들어가는 곳 } }- 주의 :
final을 사용하는 경우required를 사용하지 않는다. 더이상 상속을 하지 않기 때문이다. - 주의 : 동일한 초기화를 상속받는 하위 클래스는
required와override모두 사용해야 한다.
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
required override init() {
}
}
프로토콜을 타입으로 사용하기
- 프로토콜이 실제로 어떤 기능을 구현하는 것은 아니다. 그럼에도 불구하고 타입으로 사용이 가능하다.
타입이기 때문에 타입으로 사용가능한 곳에 사용이 가능하다.
- 인자 타입이나 반환 타입 => 함수, 메소드, 초기화
- 상수, 변수, 프로퍼티
- array, dictionary, 다른 container
주의 : 프로토콜은 타입이다. 따라서 대문자로 시작해야한다. (Int, String, Double, ...)
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
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
Delegation
- 디자인 패턴 중에 하나로 다른 인스턴스나 타입에 자신의 역할을 넘기는 것을 말한다.
- 위임할 책임을 인캡슐레이션하는 프로토콜을 정의하고 이를 구현하는 방식으로 사용한다.
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11;
board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11;
board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
- 설명
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is using a \(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Init) {
++numberOfTurns
print("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
print("the game lasted for \(numberOfTurns) turns")
}
}
예제
Quiz
- 정의에 대한 질문
- 용도에 대한 질문
- 예제 관련