apple/UIKit & ReactiveX

[iOS] RxDelegateProxy + WebSocket (Starscream)

lgvv 2022. 1. 17. 01:20

[iOS] RxDelegateProxy + WebSocket (Starscream)

 

인턴을 하고 있는데, 기존 사이드 프로젝트에서 Starscream을 많이 다루어서 Rx로 변환하는 과정을 담당.

RxStascream의 Rx 버전 문제로 인하여 직접 proxy로 만들어주어야 하는 상황.

 

개발환경

  • Xcode 13.0
  • Swift 5
  • CocoaPod
- Differentiator (3.1.0)
  - RxCocoa (4.5.0):
    - RxSwift (>= 4.4.2, ~> 4.4)
  - RxDataSources (3.1.0):
    - Differentiator (~> 3.0)
    - RxCocoa (~> 4.0)
    - RxSwift (~> 4.0)
  - RxSwift (4.5.0)
  - SnapKit (5.0.1)
  - Starscream (4.0.4)
  - Then (2.7.0)

 

Starscream 반드시 4.0.0이상 버전으로 사용. 3.x 버전과 아주 큰 차이로 proxy를 사용할 수 없을 수도 있으니 주의할 것.

 

🟠 아래는 예제코드

websoketProxy성공.zip
1.04MB

 

기존에 methodInvoked를 이용한 proxy 만드는 방법은 다들 보았고, 크게 어렵지 않을탠데, Websocket은 그럴 수 없어서 약간의 트릭을 이용했는데, 한번 보도록 하자

 

 

 

✅ WebSocketDelegateProxy.Swift

import Foundation
import RxSwift
import RxCocoa
import Starscream

public class RxWebSocketDelegateProxy: DelegateProxy<WebSocket, WebSocketDelegate>, DelegateProxyType, WebSocketDelegate {
    
    var subject = PublishSubject<WebSocketEvent>()
    
    public func didReceive(event: Starscream.WebSocketEvent, client: WebSocket) {
        subject.onNext(event)
            print(event)
    }
    
    public static func currentDelegate(for object: WebSocket) -> WebSocketDelegate? {
        return object.delegate
    }
    
    public static func setCurrentDelegate(_ delegate: WebSocketDelegate?, to object: WebSocket) {
        object.delegate = delegate
    }
    
    public static func registerKnownImplementations() {
        self.register { inputViewController -> RxWebSocketDelegateProxy in
            RxWebSocketDelegateProxy(parentObject: inputViewController, delegateProxy: self)
        }
    }
}



extension WebSocket: ReactiveCompatible { } // WebSocket을 Rx로 사용할 수 있게 바꿔줌

extension Reactive where Base: WebSocket {

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

    public var text: Observable<String> {
        return self.response
            .filter {
                switch $0 {
                case .text:
                    return true
                default:
                    return false
                }
            }
            .map {
                switch $0 {
                case .text(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()
        }
    }
}

여기 부분의 코드는 WebSocket을 Proxy로 감싸는 부분이야.

 

🟠 WebSocketManager.swift

import Foundation
import RxSwift
import RxCocoa
import Starscream

class WebSocketManager {
    static let shared = WebSocketManager()

    var webSocket: WebSocket!

    init() {
        print("WebSocketManager init")
        setupWebSocket()
    }

    func setupWebSocket() {
        print("setupWebSocket function")
        let url = "{Your Server}"
        var request = URLRequest(url: URL(string: url)!)
        request.addValue("Your Server", forHTTPHeaderField: "Origin")
        request.timeoutInterval = 10
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")

        webSocket = WebSocket(request: request)
    }

    func connect() {
        print("connect")
        webSocket.connect()
    }

    func disconnect() {
        print("disconnect")
        webSocket.disconnect()
    }

    func write(_ text: String) {
        print("write")
        webSocket.write(string: text)
    }
}

Decoding 부분을 따로 두고 있고, 최근에 MVVM + Rx 좋은 코드들에 비하면 잘 작성한 코드는 아니지만 학습하면서 이렇게 작성.

 

🟠 ViewModel

      // ViewModel
      
      self.responseDriver = WebSocketManager.shared
            .webSocket.rx.response
            .asDriver(onErrorJustReturn: .error(nil))

 

위 코드처럼 사용.

 

🟠 ViewController

     // ViewController
     
     viewModel.responseDriver
            //.debug("outputDriver VC")
            .drive ( onNext: {
                switch $0 {
                case .connected:
                    print("커넥트 성공한다면 나는 정말정말 좋을거 같아요.~_~")
                    // TODO
                case .disconnected(let text, _):
                    print("디스커넥트도 과연 성공... 했습니다.\(text)")
                case .text(let text):
                    print("메시지가 날아왔을까요? \(text)")
                    // 디코더로 보내자.
                    self.viewModel.decodeSubject.onNext(text)
                case .cancelled:
                    print("취소")
                default: break
                }
            })
            .disposed(by: bag)

 

ViewController에서는 이렇게 사용하면 데이터를 받을 수 있음.