[iOS]Protocol

Protocol 🤷🏻‍♂️

https://docs.swift.org/swift-book/LanguageGuide/Protocols.html

 

Protocols — The Swift Programming Language (Swift 5.6)

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 tho

docs.swift.org

정의 ▾

  • 위 링크에선 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