[Swift] 딜리게이션 패턴(Delegation Pattern)
스위프트(Swift)는 Objective-C의 많은 기능들을 새롭게 포장해서 제공해 주고 있는데 프로토콜도 그 중 하나이다. 이 프로토콜(Protocols)과 딜리게이션 패턴(Deleagation Pattern)에 대해 간단히 정리해 본다.
프로토콜에 관해서는 별도로 정리한 내용이 있으니 잘 모른다면 아래 링크를 읽어보자.
[관련글] Swift - 프로토콜(Protocols)
딜리게이션 패턴은 특정 클래스가 하는 일 중 일부를 자신의 커스텀 클래스에서 대신 처리하기 위해 사용할 수 있는 패턴이다. 다른 언어와 비교하자면 클래스 추상화(Class Abstraction)와 비슷하게 생각 할 수도 있다. 물론 정확한 용어로는 delegate의 의미인 '위임한다' 라는 표현이 맞겠지만...
이제 이 클래스를 이용해 보자.
딜리게이션 패턴은 이처럼 특정 클래스에서 하는 일 중 일부를 자신의 클래스에서 구현 할 수 있도록 하는 디자인 패턴이다.
하지만 스위프트에는 제약사항이 있다. 해당 프로토콜이 반드시 Objective-C 쪽에 알려질 수 있도록 만들어야 한다는 점이다. 좀 귀찮지만 위 코드를 아래처럼 수정하자.
클래스에도 @objc를 명시한 이유는 프로토콜에서 사용하는 인자로 전달되는 클래스이기 때문이다. 빼 먹은 경우 Xcode에서 친절하게 해결방법을 제시해 주니 별로 어렵게 생각 할 필요는 없다.
BlahClass의 somethingAnother 메소드 구현 내용을 잘 보자. 옵셔널 체인을 이용해 딜리게이트의 호출을 유연하게 처리하고 있다. 즉, 위임받은 오브젝트에 blahBlah 라는 메서드가 구현되어 있지 않으면 somethingAnother는 아무 일도 하지 않는다. 죽지 않는것만 해도 어디인가. :-)
이제 이 BlahClass를 사용하는 예제를 보자.
옵셔널 메서드 구현은 확실히 스위프트가 편하다. Objective-C 라면 responseToSelector를 이용해 귀찮게 코딩을 해야 되기 때문에...
그리고 위임을 받는 인스턴스를 반드시 클래스 타입으로 제한하는 것도 생각해 볼 문제이다. 구조체일 경우 레퍼런스 관리에서 클래스와는 다른 현상이 발생 할 수 있으니 미연에 방지하고자 하는 거라고 생각하자. 만약 프로토콜의 메서드에서 해당 프로토콜을 이용하는 클래스 오브젝트 레퍼런스를 받아서 써야 한다면 반드시 클래스 전용 프로토콜이 되어야 할 것이다. (위 예에서 프로토콜의 blahBlah 메서드가 이 경우이다)
그래서 최종적으로 수정된 코드는 아래와 같다.
[관련글] Swift - 프로토콜(Protocols)
[관련글] Swift Memory Management #3 구조체(struct)와 클래스(class)
프로토콜에 관해서는 별도로 정리한 내용이 있으니 잘 모른다면 아래 링크를 읽어보자.
[관련글] Swift - 프로토콜(Protocols)
딜리게이션 패턴(Delegation Pattern)
iOS나 OSX용 앱을 만들 때 delegate 라 불리우는 것을 종종 사용해 봤을 것이다. 이 delegate 라는 용어가 사용되면 그게 바로 딜리게이션 패턴을 구현한 것이라고 볼 수 있다.딜리게이션 패턴은 특정 클래스가 하는 일 중 일부를 자신의 커스텀 클래스에서 대신 처리하기 위해 사용할 수 있는 패턴이다. 다른 언어와 비교하자면 클래스 추상화(Class Abstraction)와 비슷하게 생각 할 수도 있다. 물론 정확한 용어로는 delegate의 의미인 '위임한다' 라는 표현이 맞겠지만...
참고로 딜리게이션 패턴(Delegation Pattern)은 데코레이터 패턴(Decorator Pattern)의 하위 분류로서 '확장 기능 제공' 을 위한 디자인 패턴 중 하나이다.딜리게이션 패턴을 구현한 예를 보자. 우선은 딜리게이트의 규칙을 정의한 프로토콜과 이 프로토콜을 사용하는 클래스이다.
protocol BlahProtocol {
func blah(blahObject: BlahClass)
}
class BlahClass {
var delegate: BlahProtocol?
func something() {
self.delegate?.blah(self)
}
}
이 내용이 사실 딜리게이션 패턴의 전부이다. 위의 BlahClass에서 하는 일 중 특정한 경우 BlahProcotol에 명시된 blah 라는 메소드 호출 할 수 있도록 기술되어 있다.이제 이 클래스를 이용해 보자.
class CustomClass: BlahProtocol {
let blah = BlahClass()
init() {
self.blah.delegate = self
self.blah.something()
}
func blah(blahObject: BlahClass) {
println("Calling with blah protocol")
}
}
let cc = CustomClass()
// 콘솔에 "Calling with blah protocol" 이 찍힌다.
여기서 구현한 CustomClass는 BlahProcotol의 규칙에 따라 특정한 메소드를 구현했다. 이렇게 함으로써 내부에서 쓰는 BlahClass 인스턴스의 delegate에 자신을 위임(delegate)했다.딜리게이션 패턴은 이처럼 특정 클래스에서 하는 일 중 일부를 자신의 클래스에서 구현 할 수 있도록 하는 디자인 패턴이다.
Optional Protocol Method
프로토콜에는 optional을 이용해 필수적으로 구현하지 않아도 되는 옵셔널 메서드 원형을 제시 할 수 있다. Objective-C 의 프로토콜에서 '@optional' 키워드를 붙여서 쓰던 것과 비슷하다.하지만 스위프트에는 제약사항이 있다. 해당 프로토콜이 반드시 Objective-C 쪽에 알려질 수 있도록 만들어야 한다는 점이다. 좀 귀찮지만 위 코드를 아래처럼 수정하자.
@objc
protocol BlahProtocol {
func blah(blahObject: BlahClass)
// 아래는 optional이다.
optional func blahBlah(blahObject: BlahClass, whyBlahBlah: String)
}
@objc
class BlahClass {
var delegate: BlahProtocol?
func something() {
self.delegate?.blah(self)
}
func somethingAnother() {
// optional이기 때문에 없을 수도 있다.
// 그래서 옵셔널 타입 형식으로 호출한다.
self.delegate?.blahBlah?(self, whyBlahBlah: "because blah blah")
}
}
프로토콜에 @objc를 명시했다. 그리고 옵셔널 메서드인 blahBlah도 만들었다. optional 이라고 써 있으니 바로 이해 가능할 것이다.클래스에도 @objc를 명시한 이유는 프로토콜에서 사용하는 인자로 전달되는 클래스이기 때문이다. 빼 먹은 경우 Xcode에서 친절하게 해결방법을 제시해 주니 별로 어렵게 생각 할 필요는 없다.
BlahClass의 somethingAnother 메소드 구현 내용을 잘 보자. 옵셔널 체인을 이용해 딜리게이트의 호출을 유연하게 처리하고 있다. 즉, 위임받은 오브젝트에 blahBlah 라는 메서드가 구현되어 있지 않으면 somethingAnother는 아무 일도 하지 않는다. 죽지 않는것만 해도 어디인가. :-)
이제 이 BlahClass를 사용하는 예제를 보자.
class CustomClass: BlahProtocol {
let blah = BlahClass()
init() {
self.blah.delegate = self
self.blah.something()
self.blah.somethingAnother()
}
func blah(blahObject: BlahClass) {
println("Calling with blah protocol")
}
func blahBlah(blahObject: BlahClass, whyBlahBlah: String) {
println("Why = \(whyBlahBlah)")
}
}
let cc = CustomClass()
// 콘솔에 "Calling with blah protocol" 및
// "Why = because blah blah" 가 찍힌다.
프로토콜 메서드를 구현한 것 중 두 번째인 blahBlah 메소드는 옵셔널이기 때문에 구현하지 않아도 무관하다. BlahClass의 somethingAnother 메서드에서 이 blahBlah를 호출하는데, 옵셔널 체인을 이용해 존재하지 않으면 실행하지 않도록 구현되어 있기 때문이다.옵셔널 메서드 구현은 확실히 스위프트가 편하다. Objective-C 라면 responseToSelector를 이용해 귀찮게 코딩을 해야 되기 때문에...
weak 및 class-only protocol
상황에 따라 다르겠지만, 딜리게이션 패턴에서 딜리게이트(delegate)라는 프로퍼티는 weak가 되어야 할 때도 있다. 잘못하면 Retain Cycles가 발생 할 수도 있기 때문이다. 강조하지만 상황에 따라서이지 필수는 아니다. 잘 생각해서 구현하자.그리고 위임을 받는 인스턴스를 반드시 클래스 타입으로 제한하는 것도 생각해 볼 문제이다. 구조체일 경우 레퍼런스 관리에서 클래스와는 다른 현상이 발생 할 수 있으니 미연에 방지하고자 하는 거라고 생각하자. 만약 프로토콜의 메서드에서 해당 프로토콜을 이용하는 클래스 오브젝트 레퍼런스를 받아서 써야 한다면 반드시 클래스 전용 프로토콜이 되어야 할 것이다. (위 예에서 프로토콜의 blahBlah 메서드가 이 경우이다)
그래서 최종적으로 수정된 코드는 아래와 같다.
@objc
// 클래스 전용 프로토콜(class-only procotols)
protocol BlahProtocol: class {
func blah(blahObject: BlahClass)
optional func blahBlah(blahObject: BlahClass, whyBlahBlah: String)
}
@objc
class BlahClass {
// 약한 참조(weak reference delegate)
weak var delegate: BlahProtocol?
func something() {
self.delegate?.blah(self)
}
func somethingAnother() {
self.delegate?.blahBlah?(self, whyBlahBlah: "because blah blah")
}
}
이 정도면 이제 Swift 뿐만 아니라 Objective-C 코드에서도 이 클래스를 가져다 쓰는데 여러운 문제는 없을 거란 생각이 든다. -_-;;;
[관련글] Swift - 프로토콜(Protocols)
[관련글] Swift Memory Management #3 구조체(struct)와 클래스(class)
댓글
그냥 다 implement하는 대신 아무 기능도 하지 않는 코드를 삽입하는건 어떻게 생각하세요?
Objective-C를 사용하지 않는 앱이라는 가정하에서요!
...
빈 구현 대신 protocol의 기본(공통?)적인 구현부를 extension을 이용해 미리 구현해 둘 수 있습니다. 아마도 비슷한 이야기지 않을까 생각되네요.