본문 바로가기

카테고리 없음

Delegate / Protocol

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"

 

 

참조링크

 

Delegation

Using Delegates to Customize Object Behavior | Apple Developer Documentation

Documentation

 

 

 

 

 

 

 

728x90