티스토리 뷰

Swift

[Swift] Delegation이란?

Kim_Baechu 2021. 4. 9. 16:44

Delegation은 클래스나 구조체가 다른 타입의 인스턴스에 책임을 양도하도록하는 디자인패턴입니다.

이 디자인패턴은 위임된 기능을 제공하기 위해 delegate이 보장되도록 위임된 책임을 캡슐화하는 프로토콜을 정의함으로써 구현됩니다.

Delegation은 특정 액션이나 외부 소스로부터 소스의 유형을 알 필요 없이 데이터를 검색할 때 사용할 수 있습니다.

 

아래는 주사위게임 예시입니다.

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)
}

DiceGame 프로토콜은 주사위를 사용하는 어떤 게임에서든 사용할 수 있습니다.

 

DiceGame Delegate 프로토콜은 DiceGame의 진행 상황을 추적하기 위해 채택할 수 있습니다.

강한 참조 사이클을 막기 위해 delegates를 약한 참조로 선언합니다.프로토콜을 클래스 전용으로 하려면 뒷부분에 SnakesAndLaders 클래스에서 해당 delegate가 약한 참조를 사용해야 한다고 선언합니다.클래스 전용 프로토콜은 AnyObject에서 상속으로 표시됩니다.

 

아래는 Control Flow가 도입된 Snakes and Ladders 게임입니다.주사위 굴리기에 주사위 인스턴스를 사용하고, DiceGame 프로토콜을 채택하고, DiceGameDelegate에게 진행상황을 알립니다.

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)
    }
}

이 게임은 SnakesAndLaders라고 불리는 클래스로 wrap up 되있는데, 이는 DiceGame 프로토콜을 채택합니다. 프로토콜을 준수하기 위해 gettable dice 속성과 play() 메소드를 제공합니다.(dice 프로퍼티는 초기화 후 변경할 필요가 없으므로 let으로 선언되며 프로토콜은 gettable만 요구하면 됩니다.)

SnakesAndLaders게임 보드 설정은 클래스의 init() 이니셜라이저 내에서 수행됩니다.
모든 게임 로직은 프로토콜의 플레이 방식으로 이동되며, 프로토콜의 필요한 주사위 프로퍼티를 사용하여 주사위 굴리기 값을 제공한다.

게임을 플레이하기 위해 delegate가 필요하지 않기 때문에 delegate은 optional DiceGameDelegate로 정의됩니다.
optional이기 때문에 deleagte은 자동으로 nil로 설정됩니다. 그런 다음 게임 instantiator는 속성을 적절한 delegate로 설정할 수 있습니다. DiceGameDelegate 프로토콜은 클래스 전용이므로, 참조 사이클을 방지하기 위해 weak로 선언할 수 있습니다.

DiceGameDelegate는 게임의 진행 상황을 추적하기 위한 세 가지 방법을 제공합니다.
이 세 가지 방법은 위의 play() 방식 내에서 게임 로직에 통합되었으며, 새로운 게임이 시작되거나, 새로운 턴이 시작되거나, 게임이 종료될 때 호출됩니다.

delegate가 optional이기 때문에, play() 메소드는 delegate에게 메서드를 호출할 때마다 optional chaining을 사용합니다.
delegate가 nil이면 오류 없이 정상적으로 실패합니다.deleagte가 nil이 아닌 경우 deleagte 메서드가 호출되고 매개 변수로 SnaksAndLaders 인스턴스가 전달됩니다.

 

아래는 DiceGameTracker입니다.

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: Int) {
        numberOfTurns += 1
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(_ game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}

DiceGameTracker는 DiceGameDelegate에서 요구하는 세 가지 방법을 모두 구현합니다.

게임에서 사용한 턴 수를 추적하기 위해 이러한 방법을 사용합니다.

게임이 시작되면 numberOfTurns을 0으로 재설정하고, 새로운 턴이 시작될 때마다 이를 증가시키며, 게임이 종료된 후 총 턴 수를 출력합니다.

위에 표시된 gameDidStart(_:)의 구현에서는 게임 매개 변수를 사용하여 게임 시작 정보를 프린트합니다.

게임 파라미터는 SnaksAndLaders가 아닌 DiceGame의 타입을 가지며, 따라서 GameDidStart(_:)는 DiceGame 프로토콜의 일부로 구현된 메소드와 프로퍼티만 액세스하고 사용할 수 있습니다.

메소드는 기본 인스턴스의 유형을 쿼리(?)하기 위해 타입 캐스팅을 사용할 수 있습니다. 

게임이 실제로 SnaksAndLaders의 인스턴스인지 확인하고, 그렇다면 적절한 메시지를 인쇄합니다.

gameDidStart(_:) 메서드는 전달된 게임 매개 변수의 주사위 프로퍼티에도 액세스합니다.

게임은 DiceGame프로토콜에 부합한다고 알려져 있기 때문에 주사위 프로퍼티가 보장되므로, 게임 DidStart(_:) 방식은 어떤 게임을 하고 있는지에 관계없이 주사위 프로퍼티에 액세스하여 프린트할 수 있습니다.

 

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns

 

 

+내용 추가

Delegate의 Retain Cycle

 

protocol DelegateProtocol {
	//sth
}

class Child {
    var delegate: DelegateProtocol?
}

class Parent: DelegateProtocol {
    let child = Child()
    func delegate(){
        child.delegate = self
    }
}

이러한 상황에서 Parent = nil이 되어도 childe의 delegate가 사라지지 않아서 메모리 누수가 생깁니다.

그러므로 weak var delegate를 사용해야합니다.

댓글
공지사항