apple/UIKit & ReactiveX

[iOS] RxDelegateProxy 2편 (feat. websocket + objc가 아닌 경우)

lgvv 2022. 1. 12. 16:15

✅ 이번 시간에는 RxDelegateProxy에 대해서 알아볼 예정이야.

기존에 작성한 코드는 objc로 protocol이 구현되어 있어서 바로 사용할 수 있었지만, 그런데.. protocol objc가 아니라면 어떻게 해야할까?

 

자 이 부분에서 내가 엄청나게 ... 시간을 많이 ... 들여서 공부를 한 부분이야.

 

우선 Apple 자체에서 지원해주는 것들은 NSObjectProtocol를 상속받고 있어서 우리는 쉽게 objc로 사용할 수 있어.

하지만 외부에서 사용하는 프레임워크 중에는 objc를 사용할 수 없어서 proxy를 만드는데에 애를 먹는 경우가 있는데,

특히 Starscream이 그렇게 구현되어 있지 않아서 고민을 해야했지

 

✅ 그렇다면 어떻게 구현할 수 있을까? 

내가 생각한 두가지 설계 방법

1️⃣ starscream에서 delegate를 통해 text를 받은 후 이걸 subject로 내보낸다.

2️⃣ RxStarscream을 보면서 objc가 아니라도 흘려 보내는 방법을 공부해서 그 방법을 사용한다.

 

🟠 starscream에서 delegate를 통해 text를 받은 후 이걸 subject로 내보낸다.

extension WebSocketManager: WebSocketDelegate {
	
    var inputSubject: PublishSubject<String>

    func websocketDidConnect(socket: WebSocketClient) { print("connected") }
    
    func websocketDidDisconnect(socket: WebSocketClient, error: Error?) { print("disconnected") }
    
    func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
        inputSubject.onNext(text) // 바로 이 과정
        // 이렇게 흘려주어서 다른쪽에서 inputSubject에 bind해서 데이터를 rx로 사용하는 방법 
    }
    
    func websocketDidReceiveData(socket: WebSocketClient, data: Data) { print("data: \(data)") }
}

다만 이 방식의 단점은 Network와 ViewModel 사이의 의존성 문제 MVVM에서 약간은 이상한 아키텍쳐를 갖게 된다는 것이야.

 

🟠 RxStarscream을 보면서 objc가 아니라도 흘려 보내는 방법을 공부해서 그 방법을 사용한다.

RxStarscream의 구현부를 직접 보도록 하자.

근데 얘도 분석해 보니까 똑같이 subject를 이용해서 extension Reactive를 활용해서 내보내고 있어.

//
//  Created by Guy Kahlon.
//

import Foundation
import RxSwift
import RxCocoa
import Starscream

public enum WebSocketEvent {
    case connected
    case disconnected(Error?)
    case message(String)
    case data(Data)
    case pong
}

public class RxWebSocketDelegateProxy<Client: WebSocketClient>: DelegateProxy<Client, NSObjectProtocol>, DelegateProxyType, WebSocketDelegate, WebSocketPongDelegate {

    private weak var forwardDelegate: WebSocketDelegate?
    private weak var forwardPongDelegate: WebSocketPongDelegate?

    fileprivate let subject = PublishSubject<WebSocketEvent>()

    required public init(websocket: Client) {
        super.init(parentObject: websocket, delegateProxy: RxWebSocketDelegateProxy.self)
    }

    public static func currentDelegate(for object: Client) -> NSObjectProtocol? {
        return object.delegate as? NSObjectProtocol
    }

    public static func setCurrentDelegate(_ delegate: NSObjectProtocol?, to object: Client) {
        object.delegate = delegate as? WebSocketDelegate
        object.pongDelegate = delegate as? WebSocketPongDelegate
    }

    public static func registerKnownImplementations() {
        self.register { RxWebSocketDelegateProxy(websocket: $0) }
    }

    public func websocketDidConnect(socket: WebSocketClient) {
        subject.onNext(WebSocketEvent.connected)
        forwardDelegate?.websocketDidConnect(socket: socket)
    }

    public func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
        subject.onNext(WebSocketEvent.disconnected(error))
        forwardDelegate?.websocketDidDisconnect(socket: socket, error: error)
    }

    public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
        subject.onNext(WebSocketEvent.message(text))
        forwardDelegate?.websocketDidReceiveMessage(socket: socket, text: text)
    }

    public func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
        subject.onNext(WebSocketEvent.data(data))
        forwardDelegate?.websocketDidReceiveData(socket: socket, data: data)
    }

    public func websocketDidReceivePong(socket: WebSocketClient, data: Data?) {
        subject.onNext(WebSocketEvent.pong)
        forwardPongDelegate?.websocketDidReceivePong(socket: socket, data: data)
    }

    deinit {
        subject.onCompleted()
    }
}

extension Reactive where Base: WebSocketClient {

    public var response: Observable<WebSocketEvent> {
        return RxWebSocketDelegateProxy.proxy(for: base).subject
    }

    public var text: Observable<String> {
        return self.response
            .filter {
                switch $0 {
                case .message:
                    return true
                default:
                    return false
                }
            }
            .map {
                switch $0 {
                case .message(let message):
                    return message
                default:
                    return String()
                }
        }
    }

    public var connected: Observable<Bool> {
        return response
            .filter {
                switch $0 {
                case .connected, .disconnected:
                    return true
                default:
                    return false
                }
            }
            .map {
                switch $0 {
                case .connected:
                    return true
                default:
                    return false
                }
        }
    }

    public func write(data: Data) -> Observable<Void> {
        return Observable.create { sub in
            self.base.write(data: data) {
                sub.onNext(())
                sub.onCompleted()
            }

            return Disposables.create()
        }
    }

    public func write(ping: Data) -> Observable<Void> {
        return Observable.create { sub in
            self.base.write(ping: ping) {
                sub.onNext(())
                sub.onCompleted()
            }

            return Disposables.create()
        }
    }

    public func write(string: String) -> Observable<Void> {
        return Observable.create { sub in
            self.base.write(string: string) {
                sub.onNext(())
                sub.onCompleted()
            }

            return Disposables.create()
        }
    }
}

 

구조를 살펴 보았는데, 결국은 얘도 subject를 흘려보내나 Reactive Extension을 활용해 더 구조를 안정화 시킨점이 다르다..!

그럼 이제 Reactive Extension으로 가보도록 하자!