[ReactiveX][RxSwift]Delegate 패턴을 Rx로 바꾸기
이 글은 이해가 낮은 상태에서 참고용으로 작성하였습니다. 잘못된 내용이 있을 수 있습니다.
RxCocoa를 사용하다보면, Delegate로 사용해야 할 메소드가 확장되어 사용되는 것을 볼 수 있습니다. 예를 들면, UITableView의 didSelectRowAtIndexPath
메소드를 감싼 rx_itemSelected 메소드 등이 있습니다.
Delegate에서 Observable 메소드로 변경하여, Rx에서 사용할 수 있도록 해봅시다. 예제로 사용할 CustomClassDelegate 프로토콜, CustomClass 클래스를 선언합니다.
@objc protocol CustomClassDelegate: class {
optional func willStart(customClass: CustomClass, _ str: String)
optional func DidEnd(customClass: CustomClass, _ str: String)
}
class CustomClass: NSObject {
weak var delegate: CustomClassDelegate? = nil
func start() {
self.delegate?.willStart!(self, "WillStart")
(0...100).forEach { print($0) }
self.delegate?.DidEnd!(self, "End")
}
}
CustomClass는 start 메소드를 호출하면, 자기 자신과 “WillStart” 문자열을 반복문 시작하기 전에 전달하고, 끝나면 자기자신과 “End” 문자열을 전달합니다.
이제 CustomClassDelegate 프로토콜과 CustomClass를 Rx에서 사용할 수 있도록 합니다.
1. DelegateProxy 설정
CustomClassDelegate의 인터페이스 역할을 수행합니다.
public class RxCustomDelegateProxy: DelegateProxy, DelegateProxyType, CustomClassDelegate {
public class func currentDelegateFor(object: AnyObject) -> AnyObject? {
let custom: CustomClass = castOrFatalError(object)
return custom.delegate
}
public class func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
let custom: CustomClass = castOrFatalError(object)
custom.delegate = castOptionalOrFatalError(delegate)
}
public override class func createProxyForObject(object: AnyObject) -> AnyObject {
let custom = (object as! CustomClass)
return castOrFatalError(custom.rx_createDelegateProxy())
}
}
2. CustomClass 확장
rx_createDelegateProxy를 통해 CustomClass와 통신하는 Delegate를 만들며, rx_delegate 변수를 통해 Delegate Proxy 객체를 얻습니다.
rx_willStart, rx_DidEnd에서 rx_delegate에 Delegate Selector를 등록하여, 해당 Selector가 호출될 때 인자들을 배열로 내려줍니다.
extension CustomClass {
func rx_createDelegateProxy() -> RxCustomDelegateProxy {
return RxCustomDelegateProxy(parentObject: self)
}
var rx_delegate: DelegateProxy {
return proxyForObject(RxCustomDelegateProxy.self, self)
}
var rx_willStart: Observable<(CustomClass, String)> {
return rx_delegate.observe(#selector(CustomClassDelegate.willStart(_:_:))).map { a in
let custom = try castOrThrow(CustomClass.self, a[0])
let str = try castOrThrow(String.self, a[1])
return (custom, str)
}
}
var rx_DidEnd: Observable<(CustomClass, String)> {
return rx_delegate.observe(#selector(CustomClassDelegate.DidEnd(_:_:))).map { a in
let custom = try castOrThrow(CustomClass.self, a[0])
let str = try castOrThrow(String.self, a[1])
return (custom, str)
}
}
}
3. CustomClass + Rx
위 CustomClass 확장으로 willStart, DidEnd를 구독하여 사용할 수 있습니다.
let disposeBag = DisposeBag()
let custom = CustomClass()
custom.rx_delegate.setForwardToDelegate(self, retainDelegate: false)
custom
.rx_willStart
.subscribeNext { print($0.0, $0.1) }
.addDisposableTo(disposeBag)
custom
.rx_DidEnd
.subscribeNext { print($0.0, $0.1) }
.addDisposableTo(disposeBag)
custom.start()
위의 castOrThrow 등의 메소드는 RxCocoa에 포함되어 있으며, 필요한 경우 다음 코드를 추가해주면 됩니다.
func castOptionalOrFatalError<T>(value: AnyObject?) -> T? {
if value == nil {
return nil
}
let v: T = castOrFatalError(value)
return v
}
func castOrThrow<T>(resultType: T.Type, _ object: AnyObject) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.CastingError(object: object, targetType: resultType)
}
return returnValue
}
func castOptionalOrThrow<T>(resultType: T.Type, _ object: AnyObject) throws -> T? {
if NSNull().isEqual(object) {
return nil
}
guard let returnValue = object as? T else {
throw RxCocoaError.CastingError(object: object, targetType: resultType)
}
return returnValue
}
func castOrFatalError<T>(value: AnyObject!, message: String) -> T {
let maybeResult: T? = value as? T
guard let result = maybeResult else {
rxFatalError(message)
}
return result
}
func castOrFatalError<T>(value: Any!) -> T {
let maybeResult: T? = value as? T
guard let result = maybeResult else {
rxFatalError("Failure converting from \(value) to \(T.self)")
}
return result
}
@noreturn func rxFatalError(lastMessage: String) {
// The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
fatalError(lastMessage)
}
정리
Cocoa 프레임워크에 정의된 Delegate 패턴을 위와 같이 확장하여 사용할 수 있습니다. 하지만 이 부분은 아직 이해가 높지 않아 기록용으로 남겨둡니다.