Protocol 🤷🏻♂️
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
정의 ▾
- 위 링크에선 Protocol을 아래와 같이 정의를 내렸습니다.
- A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.
- 해석하면 → "프로토콜은 특정 작업이나 기능에 적합한 메서드, 속성 및 기타 요구 사항의 청사진을 정의합니다."
More Info ▾
- 프로토콜은 해당 요구 사항의 실제 구현을 제공하기 위해 클래스(class), 구조(structure) 또는 열거(enumeration)에 의해 채택될 수 있습니다.
- 프로토콜의 요구 사항을 충족하는 모든 유형은 해당 프로토콜을 준수한다고 합니다.
- 준수 형식이 구현해야 하는 요구 사항을 지정하는 것 외에도 이러한 요구 사항 중 일부를 구현하거나 준수 형식이 활용할 수 있는 추가 기능을 구현하도록 프로토콜을 확장할 수 있습니다.
문법 📝
기본 정의 ▾
protocol SomeProtocol {
// 프로토콜 정의를 이 곳에..
}
- 채택하기
- 콜론(:) 뒤에 프로토콜 이름을 배치하여 특정 프로토콜을 채택한다고 명시합니다.
- 여러 프로토콜이 나열될 수 있고, 쉼표로 구분한다고 합니다.
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 프로토콜 정의를 이 곳에..
}
- 주의 사항
클래스에 슈퍼 클래스(상위 클래스)가 있는 경우, 슈퍼 클래스 뒤에 프로토콜을 나열해야 합니다.
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 클래스 정의를 이 곳에..
}
- 프로토콜은 속성이 저장 속성이어야 하는지 계산 속성이어야 하는지 지정하지 않고 필요한 속성 이름과 유형만 지정합니다.
- 프로토콜은 각 속성이 gettable 또는 gettable 및 settable이어야 하는지 여부를 지정합니다.
- 속성 요구 사항은 항상 var 키워드가 접두사로 붙은 변수 속성으로 선언이 되어야 합니다.
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
- 유형 속성 요구 사항에 항상 static 키워드를 접두사로 붙입니다.
- 이 규칙은 유형 속성 요구 사항 클래스로 구현될 때 class 또는 static 키워드를 접두사로 사용할 수 있는 경우에도 적용이 됩니다.
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
- 단일 인스턴스 속성 요구 사항이 있는 프로토콜의 예시 ▾
protocol FullyNamed {
var fullName: String { get }
}
- 위 프로토콜을 채택하고 준수하는 간단한 구조의 예시 ▾
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Applessd"
- 이 예는 특정 명명된 사람을 나타내는 Person이라는 struct를 정의합니다.
- 정의의 첫 번째 줄의 일부로 FullyNamed 프로토콜을 채택한다고 명시되어 있습니다.
- Person의 각 인스턴스에는 string 유형의 fullName이라는 단일 저장 속성이 있고, FullyNamed 프로토콜의 단일 요구 사항과 일치하며 Person이 프로토콜을 올바르게 준수했음을 의미합니다.
Swift는 프로토콜 요구 사항이 충족되지 않으면 컴파일 타임에 오류를 보고합니다.
- FullyNamed 프로토콜을 채택하고 준수하는 더 복잡한 클래스 예시 ▾
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"
- 이 클래스는 fullName 속성 요구 사항을 Starship에 대한 계산된 읽기 전용 속성으로 구현합니다.
- 각 Starship 클래스 인스턴스는 필수 이름과 선택적 접두사(prefix)를 저장합니다.
- fullName 속성은 접두사 값이 있는 경우 이를 사용하고 이를 이름의 시작 부분에 추가하여 Starship의 전체 이름을 만든 예시입니다.
Method 정의 ▾
- 프로토콜은 유형을 준수하여 구현하기 위해 특정 인스턴스 메서드 및 유형(type) 메서드와 정확히 같은 방식으로 프로토콜 정의의 일부로 작성되지만 중괄호나 메서드는 본문(기능 구현 코드)은 없습니다.
- 유형(type) 속성 요구 사항과 마찬가지로 유형(type) 메서드 요구 사항은 프로토콜에 정의될 때 항상 static 키워드를 접두사로 사용합니다.
- 이는 유형 메서드 요구사항이 클래스에 의해 구현될 때 class 또는 static 키워드가 접두어로 붙는 경우에도 마찬가지입니다.
protocol SomeProtocol {
static func someTypeMethod()
}
- 단일 인스턴스 메서드 요구 사항이 있는 프로토콜 정의 ▾
protocol RandomNumberGenerator {
func random() -> Double
}
- RandomNumberGenerator는 모든 준수 유형이 호출될 때마다 Double 값을 반환하는 random이라는 인스턴스 메서드를 갖도록 요구합니다.
- 위 프로토콜은 각 난수가 생성되는 방식에 대해 어떠한 구현도 하지 않습니다.
- 위 프로토콜을 채택하는 생성자가 새로운 난수를 생성하는 표준 방법을 제공하기만 하면 됩니다.
- 아래 클래스는 선형 합동 생성기로 알려진 의사 난수 생성기 알고리즘을 구현합니다.
- RandomNumberGenerator 프로토콜을 채택하고 준수하는 클래스의 구현 예시 ▾
class LinearCongruentialGenerator: RandomNumberGeneraotr {
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("난수: \(generator.random())")
// 난수: 0.3746499199817101
print("다른 난수: \(generator.random())")
// 다른 난수: 0.729023776863283
Mutating Method 요구사항 ▾
- 메서드가 속한 인스턴스를 수정해야하는 경우에 func 키워드 앞에 mutating 키워드를 넣어줍니다.
- mutating을 통해 메서드가 속한 인스턴스와 해당 인스턴스의 속성을 수정할 수 있음을 나타냅니다.
프로토콜 인스턴스 메서드 요구 사항을 mutating으로 표시하면 클래스에 해당 메서드 구현을 작성할 때 mutating 키워드를 작성할 필요가 없습니다.
mutating 키워드는 구조체(structures)와 열거형(enumerations)에서만 사용됩니다.
- 기본적인 예시
- OnOffSwitch 열거 정의
- 켜짐 & 꺼짐 표시되는 두 상태 사이를 토글합니다.
// protocol
protocol Togglable {
mutating func toggle()
}
// main
enum OnOffSwtich: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
Delegation ▾
- 클래스 또는 구조가 일부 책임을 다른 유형의 인스턴스에 넘길 수 있도록 하는 디자인 패턴입니다.
- 이는 위임된 책임을 캡슐화하는 프로토콜을 정의하여 구현됩니다.
- 주사위 기반 보드 게임 예시 ▾
- DiceGame 프로토콜 → 주사위와 관련된 모든 게임에서 채택할 수 있는 프로토콜
- DiceGameDelegate 프로토콜을 채택하여 DiceGame의 진행 사항을 추적할 수 있습니다.
- 강한 참조 순환을 방지하기 위해 delegate는 약한 참조로 선언됩니다.
- RandomNumberGenerator/LinearCongruentialGenerator는 위(단일 인스턴스 메서드 요구 사항이 있는 프로토콜 정의)를 참고하시면 됩니다.
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: AnyObject {
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 = Array(repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
weak 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)
}
}
- 초기값으로 delegate는 nil값으로 설정이 됩니다.
- 게임 인스턴스화 속성을 적절한 delegate로 설정할 수 있습니다.
- DiceGameDelegate는 클래스 전용이므로, 참조 주기를 방지하기 위해 weak var로 선언합니다.
- DiceGameDelegate는 게임 진행 상황을 추적하는 세 가지 방법을 제공합니다. ( in play() 메서드 호출 )
- 새 게임이 시작되는 경우
- 새 차례가 시작되는 경우
- 게임이 종료되는 경우
- 대리자 속성은 선택적 DiceGameDelegate이므로 play() 메서드는 delegate에서 메서드를 호출할 때마다 선택적 연결을 사용합니다.
- delegate가 nil이면 대리자 호출은 오류 없이 실패합니다.
nil이 아닌 경우 delegate의 메서드가 호출되고 SnackAndLadders 인스턴스가 매개 변수로 전달됩니다.
- DiceGameDelegate 프로토콜을 채택하는 DiceGameTracker 클래스 ▾
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(_ game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Snakes and Ladders 게임 시작")
}
print("이 게임은 \(game.dice.sides)면체 주사위를 사용합니다.")
}
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
print("주사위 결과: \(diceRoll)")
}
func gameDidEnd(_ game: DiceGame) {
print("게임은 \(numberOfTurns)턴동안 진행되었습니다.")
}
}
- 게임 시작시 numberOfTurns는 0으로 재설정하고, 턴마다 값을 증가 시키며, 게임이 끝나면 총 턴 수를 출력합니다.
- gameDidStart(_:)는 game 매개변수를 사용하여 플레이하려는 게임에 대한 일부 소개 정보를 출력합니다.
game 매개변수에는 DiceGame 유형이 있으므로 gameDidStart(_:)는 DiceGame 프로토콜의 일부로 구현된 메서드와 속성에만 액세스하고 사용할 수 있습니다. - 메서드 형식 캐스팅을 사용하여 기본 인스턴스의 형식을 쿼리할 수 있습니다.
위 예제는 SnakesAndLadders의 인스턴스인지 확인하고, 적절한 메세지를 출력합니다. - gameDidStart(_:) 메서드는 전달된 게임 매개변수의 dice 속성에서도 액세스합니다.
게임은 DiceGame 프로토콜을 준수하는 것으로 알려져 있기 때문에 dice 속성이 보장되므로 gameDidStart(_:) 메서드는 어떤 종류의 게임이 재생되는지에 관계없이 주사위의 sides 속성에 액세스 하여 인쇄할 수 있습니다.
- DiceGameTracker 작동 예시 ▾
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Snakes and Ladders 게임 시작
// 이 게임은 6면체 주사위를 사용합니다.
// 주사위 결과: 3
// 주사위 결과: 5
// 주사위 결과: 4
// 주사위 결과: 5
// 게임은 4턴동안 진행되었습니다.
'iOS_Swift.zip' 카테고리의 다른 글
[Swift]Status Bar 색상 변겅 (0) | 2022.04.21 |
---|---|
[iOS]Strong과 weak 참조 방식 (0) | 2022.02.27 |
[iOS]NotificationCenter (0) | 2022.02.18 |
[iOS]App Thinning (0) | 2022.02.16 |
[iOS]lazy 키워드 (0) | 2022.02.16 |