관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift 공식 문서] 21. Protocols (프로토콜) 본문

swift 공식 문서

[iOS - swift 공식 문서] 21. Protocols (프로토콜)

jake-kim 2021. 7. 20. 22:32

Protocol (프로토콜)

  • protocol이란 특정 작업이나 기능에 맞게 method, property 및 기타 요구 사항의 청사진을 정의
  • protocol은 해당 요구 사항의 실제 구현을 제공하기 위해 class, struct, enum에 의해 'conform'될 수 있는 것
  • protocol을 채택하는 것을 swift에서 'conform' 명명

protocol에서 property 정의

  • let은 불가, var만 가능
  • { get set }이나 { get } 속성 제공
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}
  • protocol에서 static 정의
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

protocol에서 Method 정의

  • instance method
protocol RandomNumberGenerator {
    func random() -> Double
}
  • type method
protocol SomeProtocol {
    static func someTypeMethod()
}

protocol에서 mutating method 정의

  • mutating func 키워드 사용
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에서 init 정의

  • init을 정의한 protocol을 conform하는 곳에서는 required 키워드가 필수
protocol SomeProtocol {
    init(someParameter: Int)
}

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}
  • sub class가 상속, conform하고 있는 class와 protocol 각각 모두 시그니쳐도 같은 init() {}를 구현하고 있을 때,
    required override 키워드로 선언
protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    required override init() {
    }
}

Type으로서의 protocol

  • Type은 Parent이지만 주입받는 객체는 Child - 업캐스팅
// 최종 캐스팅 되는 유형이 parent이면 업캐스팅, child이면 다운 캐스팅

let p: Parent = Child() // 업캐스팅 ... p는 Parent type
let c: Child = p as? Child // 다운 캐스팅 ... p는 Child type
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())")
}
  • 다운 캐스팅해서 사용 가능
var sample = Dice(generator: LinearCongruentialGenerator()) // 업 캐스팅
let sampleDownCasting = sample as? LinearCongruentialGenerator() // 다운 캐스팅
sampleDownCasting.someFunctionLinearCongruentialGenerator()

Delegate

  • Delegate는 class나 struct가 일부 책임을 다른 유형의 instance에 넘길 수 있도록 하는 디자인 패턴
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)
}
  • Strong reference cycle을 피하기 위해서 delegate는 weak로 선언
    • weak로 선언하지 않으면 delegate가 참조하는 함수들의 reference count가 상승하여 memory leak 발생을 주의
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)
    }
}

Extension으로 protocol conform 추가

  • 기존 type의 소스 코드에 액세스할 수 없는 경우라도 기존 type을 확장하여 새 프로토콜을 conform 가능
protocol TextRepresentable {
    var textualDescription: String { get }
}

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

조건적으로 protocol을 comform하는 방법

  • where키워드와 특정 타입 정의
    • Element가 TextRepresentable을 따르고 Array가 TextRepresentable을 따르고 있는 경우
extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

extension으로 protocol 채택 선언

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

Class 전용 protocol

  • AnyObject 키워드 선언
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

protocol에서 & 연산자

  • and조건이며, 아래 코드에선 Location과 Named 프로토콜을 모두 준수하는 파라미터만 인수로 채택 가능
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}

protocol에 optional 표시

  • @objc 키워드 입력: 해당 property나 method는 conform하지 않아도 되는 것
@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

 

Comments