Swift 3.0.1 가이드에 대응하는 정리글을 작성하였습니다!!!
Protocols 정리 최신버전 > http://wlaxhrl.tistory.com/58
Apple 제공 Swift 프로그래밍 가이드(2.2)의 Protocols 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.
프로토콜 2편입니다. 이번 시간에는 Delegation부터 시작합니다. 1편을 아직 안 읽으셨다면 먼저 1편을 읽어보세요.
Protocols 1편 링크 > http://wlaxhrl.tistory.com/28
Delegation
딜리게이션은 클래스/구조체가 자신의 책임 중 일부를 다른 타입의 인스턴스에게 떠넘길 수 있게 해주는(=위임할 수 있게 해주는 = hand off = delegate) 디자인 패턴이다.
딜리게이션 패턴을 구현하려면, 다른 타입에게 위임할 책임(기능)을 캡슐화하고 있는 프로토콜을 정의해야 한다. 그 프로토콜을 따르는 타입을 딜리게이트라고 하고, 딜리게이트는 위임받은 책임(기능)을 제공할 수 있다고 보장된다.
예제를 통해 알아보자. 다음은 1편에서 다뤘던 주사위(Dice) 클래스와, 주사위를 사용하는 보드게임에 쓰일 프로토콜 두 개 DiceGame, DiceGameDelegate이다.
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
}
}
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)
}
이 프로토콜을 이용하여 Control Flow 장에서 다뤘던 뱀과 사다리 게임을 구현해보겠다.
// DiceGame 프로토콜을 따르는, 주사위 게임 "뱀과 사다리" 클래스이다.
// dice 프로퍼티와 play 메서드를 어떻게 구현하고 있는지 살펴보자.
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?
// 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)
}
}
// DiceGameDelegate을 따르는 클래스를 정의함으로써,
// 게임의 시작/진행도중/끝에서 할 일들을 정의할 수 있다
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("뱀과 사다리 게임이 시작되었다")
}
print("이 게임은 \(game.dice.sides)면체 주사위를 사용한다")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
print("주사위를 굴려 \(diceRoll)이 나왔다")
}
func gameDidEnd(game: DiceGame) {
print("게임은 \(numberOfTurns)턴 만에 끝났다")
}
}
// 실제로 이런 식으로 쓰인다
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// 뱀과 사다리 게임이 시작되었다
// 이 게임은 6면체 주사위를 사용한다
// 주사위를 굴려 3이 나왔다
// 주사위를 굴려 5이 나왔다
// 주사위를 굴려 4이 나왔다
// 주사위를 굴려 5이 나왔다
// 게임은 4턴 만에 끝났다
Adding Protocol Conformance with an Extension
직접 소스코드에 접근할 수 없는 타입이라도 확장을 통해 새로운 프로토콜을 따르게 할 수 있다. 확장을 통해 기존 타입에 새로운 프로퍼티, 메서드, 서브스크립트를 더할 수 있기 때문이다.
<note> 확장을 통해 기존 타입이 새로운 프로토콜을 따르게 되면, 그 전에 생성되었던 그 타입의 인스턴스들도 자동으로 프로토콜을 따르게 된다.
예제를 통해 알아보자.
protocol TextRepresentable {
var textualDescription: String { get }
}
// Dice 클래스를 확장하여 TextRepresentable 프로토콜을 따르게 해보자
extension Dice: TextRepresentable {
var textualDescription: String {
return "\(sides)면체 주사위"
}
}
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription) // "12면체 주사위"
// SnakesAndLadders 클래스를 확장하여 TextRepresentable 프로토콜을 따르게 해보자
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "\(finalSquare)개의 말판을 가지고 하는 뱀과 사다리 게임"
}
}
let game = SnakesAndLadders()
print(game.textualDescription) // "25개의 말판을 가지고 하는 뱀과 사다리 게임"
<확장을 통해 프로토콜 적용을 선언하기>
이미 프로토콜의 요구조건을 만족하고 있지만 프로토콜을 따른다고 명시하지 않은 경우, 빈 확장을 통해 프로토콜 적용을 선언하여 프로토콜을 따르게 할 수 있다.
// 프로토콜 따른다는 명시가 없으면
// 아무리 요구사항을 만족하더라도 그 프로토콜을 따르는 것이 아니다
struct Hamster {
var name: String
var textualDescription: String {
return "\(name)라는 이름의 햄스터"
}
}
// 빈 확장 + 프로토콜 적용을 명시
extension Hamster: TextRepresentable {}
// 이제 Hamster 구조체는 TextRepresentable 프로토콜을 따른다
let someHamster = Hamster(name: "햄토리")
let somethingTextRepresentable: TextRepresentable = someHamster
print(somethingTextRepresentable.textualDescription) // "햄토리라는 이름의 햄스터"
프로토콜 타입의 콜렉션들Collections of Protocol Types
프로토콜 타입도 다른 타입과 마찬가지로 콜렉션에 저장될 타입으로 사용될 수 있다. 예제를 보자.
let things: [TextRepresentable] = [game, d12, someHamster]
for thing in things {
print(thing.textualDescription)
}
// "25개의 말판을 가지고 하는 뱀과 사다리 게임"
// "12면체 주사위"
// "햄토리라는 이름의 햄스터"
프로토콜 상속
프로토콜은 하나 이상의 프로토콜을 상속받을 수 있다. 물론 상속받은 뒤 자신만의 요구사항을 더할 수도 있다. 상속 문법은 클래스 상속 문법과 유사하고, 여러 개의 프로토콜을 상속받을 때는 쉼표로 구분한다.
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 프로토콜 정의...
}
TextRepresentable 프로토콜을 상속받는 프로토콜을 하나 살펴보자.
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
// PrettyTextRepresentable 프로토콜을 따르기 위해서는
// TextRepresentable의 요구사항인 textualDescription 프로퍼티와
// PrettyTextRepresentable의 요구사항인 prettyTextualDescription 프로퍼티를
// 모두 구현해야 한다
}
// 앞에서 확장을 통해 SnakesAndLadders가 TextRepresentable를 따르게 했었다
// 만약 그 부분을 주석처리 한다면 다음 코드만으로는 PrettyTextRepresentable의 요구조건을 만족시킬 수 없다
extension SnakesAndLadders: PrettyTextRepresentable {
var prettyTextualDescription: String {
var output = textualDescription + ":\n"
for index in 1...finalSquare {
switch board[index] {
case let ladder where ladder > 0:
output += "▲ "
case let snake where snake < 0:
output += "▼ "
default:
output += "○ "
}
}
return output
}
}
print(game.prettyTextualDescription)
// "25개의 말판을 가지고 하는 뱀과 사다리 게임"
// "○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○"
Class-Only 프로토콜
프로토콜을 오직 클래스만 따를 수 있도록 제한할 수 있다. (구조체나 ENUM은 적용 못하게) 프로토콜을 정의할 때 상속 리스트에 class 키워드를 추가하면 된다. 이때 class 키워드는 항상 상속 리스트의 처음에 와야 한다.
// 이 프로토콜을 구조체나 ENUM에 적용하려고 하면 컴파일 에러
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 프로토콜 정의는 여기서부터
}
<note> 프로토콜의 요구사항이 reference 타입을 가지고 동작하는 것을 가정할 경우 class-only 프로토콜을 쓰도록 하라.
이번 편에서는 딜리게이션 패턴, 확장을 통해 프로토콜을 적용하는 법 등을 알아보았다. 다음 3편에서는 프로토콜 합성부터 시작해서 나머지 사항들에 대해 알아보자.
3편을 작성하였습니다. http://wlaxhrl.tistory.com/30
'Swift 공식 가이드 > Swift 2' 카테고리의 다른 글
Generics (3) | 2016.07.10 |
---|---|
Protocols (3) (0) | 2016.06.19 |
Protocols (1) (0) | 2016.06.18 |
Extensions (0) | 2016.06.12 |
Nested Types (0) | 2016.06.07 |