티스토리 뷰

일반적으로 delegate를 이용하여서 ViewModel에 값을 전달하는 방식으로 Reactive를 하다보니 약간 뭔가 값의 흐림에 의한 프로그램이라는 이름이 아깝다는 느낌을 받게 되었습니다.


그래서 전에 알고 있던 delegate를 Rx방식으로 변경하는 방법을 하여보기로 하였습니다.


제가 지금 개발하는 환경은 RxSwift 4.x / Swift 4.x입니다.


스택오버플로우 자체에도 이런 부분들이 잘 정리가 되어 있지 않는데, 아주 예전 방식으로 해두어서 잘 적용이 안되더군요.


일단 rx_delegate라는 이름을 사용하는 방식들이 많은데, 제가 알기로는 rx.로 이름이 모두 변경이 된것으로 알고 있습니다. 간략하고 깔끔하게요.


그래서 RxSwift를 그냥 이번 기회에 최신으로 사용해 보겠다는 마음을 가지고 개발해보시는것도 나쁘지는 않을것 같습니다.


3.0에서도 동작하는것으로 알고 있습니다. Swift3 사용하시는분들 참고 하세요.


예제는 WKNavigationDelegate를 이용하는 경우입니다.


1. 기존의 delegate 사용


1
2
3
4
5
6
7
8
9
override func viewDidLoad() {
    ...
    webView.navigationDelegate = self
    ...
}
 
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    viewModel.decidePolicy(...)
}
cs


대략 이런 느낌이어서 처리를 하는 부분과 위쪽에 수행을 하는 부분들의 코드가 서로 분리가 되어 있었습니다.

뭐... 거의 이산가족의 느낌이죠.

코드 또한 저렇게 조금만 들어가 있어서 알아보기 쉽지만 나중에 중간에 코드들이 많이 들어가게 되면 위에서 무엇을 했는지 한참 드래그하고 보고 아래에 다시 내려와서 값을 정리합니다. 가독성이 엄청나게 떨어지죠.


2. 바뀌면 어떻게 되나요.


1
2
3
4
webView.rx.decidePolicy
    .subscribe(onNext: { (webview, action, handler) in
        handler(WKNavigationActionPolicy.allow)
    }).disposed(by: disposeBag)
cs


이렇게 내가 하고 싶던 delegate의 내용이 한쪽에 모이게 됩니다.

Rx특성처럼 Listener를 만들어서 사용하는 방식으로 처리가 되면서 코드의 가독성이 높아집니다.


3. 일단 import를 하여줍니다.


1
2
import RxCocoa
import RxSwift
cs


4. DelegateProxy Class를 만들어 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RxWKNavigationDelegateProxy: DelegateProxy<WKWebView, WKNavigationDelegate>, DelegateProxyType, WKNavigationDelegate {
    
    let decidePolicySubject = PublishSubject<(WKWebView, WKNavigationAction, ((WKNavigationActionPolicy) -> Swift.Void))>()
    
    open class func currentDelegate(for object: WKWebView) -> WKNavigationDelegate? {
        return object.navigationDelegate
    }
    
    open class func setCurrentDelegate(_ delegate: WKNavigationDelegate?, to object: WKWebView) {
        object.navigationDelegate = delegate
    }
    
    public init(webView: WKWebView) {
        super.init(parentObject: webView, delegateProxy: RxWKNavigationDelegateProxy.self)
    }
    
    static func registerKnownImplementations() {
        self.register { RxWKNavigationDelegateProxy(webView: $0) }
    }
    
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decidePolicySubject.onNext((webView, navigationAction, decisionHandler))
    }
}
cs


DelegateProxy를 이용하여, 어떠한 Object의 어떠한 Delegate를 이용할지에 대해서 정의하여 줍니다.

DelegateProxyType을 이용하여서 currentDelegate와 setCurrentDelegate를 정의하여 줍니다.

WKNavigationDelegate를 구현하여서 아래의 subject 를 통하여 우리가 감시하는 값을 전달하여 줍니다.


5. Reactive를 상속받습니다.


1
2
3
4
5
6
7
8
9
10
extension Reactive where Base: WKWebView {
    
    fileprivate var navigationDelegate: RxWKNavigationDelegateProxy {
        return RxWKNavigationDelegateProxy.proxy(for: base)
    }
    
    public var decidePolicy: Observable<(WKWebView, WKNavigationAction, ((WKNavigationActionPolicy) -> Swift.Void))> {
        return navigationDelegate.decidePolicySubject.asObserver()
    }
}
cs


이곳에서 delegate를 가져오는 방법을 넣어주고, 그것을 외부에 노출하는 decidePolicy를 통하여 Observable를 내보냅니다.


음? 뭔가 이상하다 싶지 않나요? 코드가 줄었다고 이야기하지만 생성한 코드는 더 많습니다.

그렇죠. 이렇게 간단하게 만들수는 있지만 전체적으로 보면 모든 코드를 Rx로 만들고 싶은 개발자의 욕심일수 있습니다. ㅎㅎ


뭐 나름 저는 제가 사용하는 ViewController의 코드가 줄고 전체적인 느낌이 다 Rx로 구동되어서 좋고 코드도 한눈에 무엇을 하는지 알수 있어서 좋은것 같습니다.


github에 전체 프로젝트 파일을 올려두었습니다.


https://github.com/outofcode/WKWebViewRx/tree/master

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함