Delegate
- Delegate 패턴: 프로그램의 한 개체가 다른 개체를 대신하거나 협력하여 작동할 수 있도록 하는 패턴
- 위임하는 개체는 대리자에게 참조를 유지하고 적절한 시간에 메세지를 보냄
- 장점
- 개체간 이벤트를 전달할 수 있음
- 결합도를 낮출 수 있음
- 재사용성을 높일 수 있음
- 단점
- 코드양이 많아짐
- 디버깅이 어려움
Cocoa Framework 속 Delegate
- 일반적으로 Delegate 개체는 Framework 개체, 대리자는 사용자 지정 컨드롤러 개체
- → 이때 Delegate 개체는 대리자에 대한 약한 참조를 유지
- Delegate를 사용하여 앱의 이벤트를 알려주는 Cocoa 개체와 상호 작용할 수 있음
- Cocoa API는 Delegate 메서드를 포함하는 프로토콜을 제공하는 경우가 많음→ 개체의 Delegate에서 메서드를 호출할 때, 대리자가 있는지 먼저 확인
- → 이벤트가 발생하면 이를 감지하여 대리자로 지정한 클래스에서 Delegate 메서드를 호출
class MyDelegate: NSObject, NSWindowDelegate {
func window(_ window: NSWindow, willUseFullScreenContentSize proposedSize: NSSize) -> NSSize {
return proposedSize
}
}
let myWindow = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 5120, height: 2880),
styleMask: .fullScreen,
backing: .buffered,
defer: false
)
myWindow.delegate = MyDelegate()
if let fullScreenSize = myWindow.delegate?.window(myWindow, willUseFullScreenContentSize: mySize) {
print(NSStringFromSize(fullScreenSize))
}
Protocol
- 준수한 타입이 구현해야하는 요구사항을 정의하는 것
- Method, Property의 청사진을 그려 기능의 요구사항을 명세
- Protocol은 클래스, 구조체, 열거형에서 채택
- 채택(adopted): : 타입. 프로토콜을 채택함으로 해당 요구 사항을 제공할 수 있다
- 준수(comform): 조건을 만족했을 때 실행. 채택한 프로퍼티의 형태를 따르겠다.
- Protocol을 확장하여 요구 사항의 일부를 구현하거나 추가 기능을 구현할 수 있음
- Protocol 장점
- 추상화가 가능
- 유연성과 재사용성을 높임
- 의존도를 낮출 수 있음
- 확장성
- Delegate Pattern 지원
- Naming : ~able, ~ protocol
Property Requirement
- Protocol은 특정 이름과 형식의 인스턴스 속성을 제공하기 위해 준수 형식을 요구할 수 있음
protocol SomeProtocol {
// 타입 프로퍼티 가능
static var someTypeProperty: Int { get set }
// 인스턴스 프로퍼티
// 읽기/쓰기 모두 필수 구현
var mustBeSettable: Int { get set }
// 읽기 전용 { get }
// 최소한 이 속성을 읽을 수 있어야 함을 의미
// -> 읽기/쓰기 가능하게 구현 가능함
var doesNotNeedToBeSettable: Int { get }
let mustBeGettable: Int { get }
}
class SomeClass: SomeProtocol {
static var someTypeProperty: Int = 1
// 읽기 전용으로만 구현하고 싶은 경우
var mustBeSettable = 0
// 1. 계산 프로퍼티 사용
var doesNotNeedToBeSettable: Int {
return mustBeSettable + 2
}
// 2. let 상수 사용
let mustBeSettable: Int = 100
}
Method Requirement
protocol SomeProtocol {
// 타입 메소드 가능
static func someTypeMethod()
// 리턴 타입 메소드
func random() -> Double
}
// Mutating Method: 해당 Protocol은 구조체, 열거형에서 채택
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case off, on
// enum안에 함수 구현 -> self를 바꾸는 함수니까 mutating이 필요
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch is now equal to .on
// 위 아래 같은 표현 / . -> 타입 추론
var Togglable: OnOffSwitch = .off // 명시적
// 타입 추론이 불가능하다!!!
var Togglable = OnOffSwitch.off // 묵시적
- enum은 인스턴스화를 하지 않는다
Initializer Requirements
protocol SomeProtocol {
init(someParameter: Int)
}
// 1. 상속가능성이 있기 때문에 required
class SuperClass: SomeProtocol {
// init() { }
required init(someParameter: Int) {
// fatalError("init(someParameter:) has not been implemented")
}
}
// 2. 클래스가 프로토콜을 채택할 때 final이라면 required를 쓰지 않아도 된다!!
final class SuperClass: SomeProtocol {
init(someParameter: Int) { }
}
// superclass를 상속을 받을 때 init을 만들 때 required init을 만들어야함- init 안만들면 상관 X
class ChildClass: SuperClass {
// SuperClass 안에 init이 있다면 override로 구현
// override init() { }
init() {
print("첫번째로 읽히고")
// 상속을 받았기 때문에 부모클래스의 init이 필요하다면 호출
super.init(someParameter : 10)
}
init(name: String) {
print("첫번째로 읽히고")
super.init(someParameter : 10)
}
// 부모의 required init 형태를 무조건 똑같이 따라야함!!
required init(someParameter: Int) {
// fatalError("init(someParameter:) has not been implemented")
super.init(someParemeter: 20)
}
}
//ChildClass안에 init 각각 사용되는 것
let child1 = ChildClass() -> init()
let child1 = ChildClass(name: "serena") -> init(name: String)
let child2 = ChildClass(someParameter: 0) -> reqired init(someParameter: Int)
Delegate Pattern with Protocol
protocol Video: AnyObject {
func uploadVideo()
}
class Youtuber {
weak var delegate: Video?
func upload() {
self.delegate?.uploadVideo()
}
}
class Editor: Video {
let youtuber = Youtuber()
init() {
youtuber.delegate = self
}
func uploadVideo() {
print("영상 업로드 했습니다.")
}
}
let editor = Editor()
editor.youtuber.upload() // 영상 업로드 했습니다.
weak var
- protocol가 Anyobject를 채택하면 class 타입에서만 사용 가능하게 제한을 거는 것
- protocol에 Anyobject를 채택하고, weak var를 사용하면 약한 참조를 함으로 순환참조를 예방
- → 당연하게 값타입은 weak를 못함
extension을 통해서 프로토콜을 채택한 타입을 확장
protocol TextRepresentable {
var textualDescription: String { get }
}
// 타입에 프로토콜을 채택해서 확장
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \\(sides)-sided dice"
}
}
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints "A 12-sided dice"
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \\(finalSquare) squares"
}
}
print(game.textualDescription)
// Prints "A game of Snakes and Ladders with 25 squares"
조건부 프로토콜 채택 → where!!!
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
print(equalNumbers.allEqual())
// Prints "true"
print(differentNumbers.allEqual())
// Prints "false"
참조링크
Using Delegates to Customize Object Behavior | Apple Developer Documentation
728x90