[iOS] RxDelegateProxy 2편 (feat. websocket + objc가 아닌 경우)
✅ 이번 시간에는 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으로 가보도록 하자!