Menu
Coddy logo textTech

위임 패턴

Coddy Swift 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 46번째.

위임은 객체가 프로토콜을 통해 일부 결정을 다른 객체인 델리게이트에게 맡길 수 있게 합니다. 객체는 자신의 작업을 수행하고, 변경될 수 있는 지점에서 델리게이트에게 요청합니다. Apple의 프레임워크는 어디에서나 이 패턴을 사용합니다:

protocol DownloadDelegate: AnyObject {
    func progress(_ percent: Int)
    func finished(_ file: String)
}

final class Downloader {
    weak var delegate: DownloadDelegate?

    func download(_ file: String) {
        for p in [50, 100] { delegate?.progress(p) }
        delegate?.finished(file)
    }
}

final class Screen: DownloadDelegate {
    func progress(_ percent: Int) { print("\(percent)%") }
    func finished(_ file: String) { print("done: \(file)") }
}

let screen = Screen()
let d = Downloader()
d.delegate = screen
d.download("photo.png")

출력:

50%
100%
done: photo.png

delegate 속성은 선택 사항이며 호출에 delegate?.를 사용하므로, 아무도 수신 대기 중이지 않을 때에도 객체가 작동합니다. 이 속성은 weak으로 선언되어 객체가 자체적으로 delegate를 계속 유지하지 않으므로, 서로를 가리키는 객체 사이의 순환 참조를 방지합니다:

weak var delegate: DownloadDelegate?   // 프로토콜이 클래스 전용이어야 함: AnyObject
delegate?.finished(file)               // delegate가 없을 때 아무 작업도 하지 않음

델리게이트는 질문에 답할 수도 있으므로, 서브클래싱하지 않고도 객체의 동작을 사용자 지정할 수 있습니다:

protocol ListDelegate: AnyObject {
    func shouldShow(_ item: String) -> Bool
}

final class ListView {
    weak var delegate: ListDelegate?
    func render(_ items: [String]) -> [String] {
        items.filter { delegate?.shouldShow($0) ?? true }
    }
}

final class HideDrafts: ListDelegate {
    func shouldShow(_ item: String) -> Bool { !item.hasPrefix("draft") }
}

let view = ListView()
let rules = HideDrafts()
print(view.render(["a", "draft b"]))
view.delegate = rules
print(view.render(["a", "draft b"]))

출력:

["a", "draft b"]
["a"]

위임은 옵서버 패턴과 다릅니다. 객체에는 질문하고 알리는 하나의 위임자가 있는 반면, 주제에는 알리기만 하는 여러 옵서버가 있을 수 있습니다.

// delegation: 하나의 파트너, 질문에 답할 수 있음
weak var delegate: TableDelegate?
// observer: 임의의 수의 리스너, 알림만 받음
private var observers: [(Event) -> Void] = []
challenge icon

챌린지

쉬움

Checkout은 결정을 내리기 위해 자신의 delegate에게 요청합니다. 프로토콜 CheckoutDelegate(클래스 전용)는 discount(for total: Int) -> Int와 didComplete(total: Int)을 가집니다. Checkout에는 weak var delegate와, 가격을 합산하고 delegate의 할인 금액(delegate가 없으면 0)을 차감한 뒤 최종 합계를 delegate에게 알리고 반환하는 pay(_ prices: [Int]) -> Int가 있습니다. 100 이상인 합계에 10% 할인을 제공하고 완료된 모든 합계를 receipts에 기록하는 delegate인 LoyaltyShop을 구현하세요.

제공된 코드는 쉼표로 구분된 가격의 줄을 읽습니다. 첫 번째 줄은 delegate 없이 결제하고 나머지 줄은 LoyaltyShop과 함께 결제하며, 각 합계를 출력하고 마지막으로 receipts: 120 90을 출력합니다.

코드는 CheckoutDelegate.swift, Checkout.swift 및 LoyaltyShop.swift에 작성하세요. main.swift에는 제공된 입력/출력 코드가 있으며 수정할 수 없습니다.

직접 해보기

// 제공된 입력/출력 코드: 그대로 유지하세요
var input: [String] = []
while let line = readLine() { input.append(line) }
let checkout = Checkout()
let shop = LoyaltyShop()
for (i, line) in input.enumerated() {
    if i == 1 { checkout.delegate = shop }
    print(checkout.pay(line.split(separator: ",").map { Int($0)! }))
}
print("receipts: " + shop.receipts.map { String($0) }.joined(separator: " "))
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: Swift 플레이그라운드