어제 새벽까지 작업했던 첫번째 시도가 실패한 이후, 드디어 오늘 업비트 웹소켓 연결 이후 JSON 파싱까지 성공하였습니다!
어제 실패한 이유는 웹소켓에서 오는 데이터를 파싱하는 방식이 서툴렀기 때문이었어요.
예를 들어서 Json 파싱을 위한 Model을 살펴보면,
업비트의 RestAPI에서는 "KRW-BTC"가 market이라는 필드명을 가진 반면, WebSocket에서는 code라는 필드명을 가지고 있어요.
그래서 Model을 수정해야 했어요.
또한 트래픽이 많은 경우 사용하는 방법으로 축약형 필드명를 소개하고 있어서, Model의 코딩키를 축약형으로 바꿔야 했어요.
또 다른 어려웠던 점은, URLSessionWebSocketTask에서 receive의 Success 케이스에는 string과 data 케이스를 가지고 있는데,
이러한 receive에서 데이터가 어떻게 오는지, 케이스를 어떻게 다루면 좋을지 감이 없었기 때문에 실패를 했었어요.
하지만 지금은 감을 잡고 성공적으로 JSON 파싱을 하였답니다.
Starscream과 URLSessionWebSocketTask 중에서 고민을 많이 하고, 찾아보았어요.
결국에는 외부 라이브러리의 의존성을 줄이는 방향으로 공부를 하는게 장기적으로 좋을 것 같아서 URLSessionWebSocketTask 으로 결정했습니다!
코드는 다음과 같아요.
**환경: iOS14, xcode 13
import Foundation
class UpbitWebSocketService: NSObject, URLSessionWebSocketDelegate {
@Published var tickers = [String:UpbitTicker]()
private var webSocket: URLSessionWebSocketTask?
private var codes = ""
// 서버와 연결하는 함수
func connect(codes: String) {
self.codes = codes
let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let url = URL(string:"wss://api.upbit.com/websocket/v1")!
webSocket = session.webSocketTask(with: url)
webSocket?.resume()
}
// 연결된 이후 서버에 메시지를 보내는 함수
func send() {
let message = """
[{"ticket":"bw"},{"type":"ticker","codes":[\(codes)]},{"format":"SIMPLE"}]
"""
webSocket?.send(.string(message), completionHandler: { error in
if let error = error {
print("Upbit send error: \(error.localizedDescription)")
}
})
}
// 연결을 유지하기 위해 60초마다 ping을 보내는 함수
func ping() {
let timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] timer in
self?.webSocket?.sendPing(pongReceiveHandler: { error in
if let error = error {
print("Upbit ping error: \(error.localizedDescription)")
}
})
}
timer.fire()
}
// 연결과 메시지 전달 이후 데이터를 받는 함수
private func receive() {
webSocket?.receive { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let message):
switch message {
case .string(let text):
print("Received text message: \(text)")
if let data = text.data(using: .utf8) {
self.onReceiveData(data)
}
case .data(let data):
print("Received binary message: \(data)")
self.onReceiveData(data)
default: break
}
self.receive()
case .failure(let error):
print("Failed to receive message: \(error.localizedDescription)")
}
}
}
// 받은 데이터를 파싱 & 가공하는 함수
private func onReceiveData(_ data: Data) {
guard let ticker = try? JSONDecoder().decode(UpbitTicker.self, from: data) else {
return print("UpbitTicker 객체 생성 에러")
}
let newDictionary = [ticker.market: ticker]
print(newDictionary)
}
// 웹소켓 연결 닫기
func close() {
webSocket?.cancel(with: .goingAway, reason: nil)
webSocket = nil
}
// 웹소켓이 성공적으로 연결되었다면 실행되는 함수
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
print("UPbit websocket connection opened.")
send()
receive()
ping()
}
// 웹소켓의 연결이 끊어졌다면 실행되는 함수
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
print("UPbit websocket connection closed.")
}
}
아직 개선할 부분이 많아서 각 함수와 역할에 대해서 자세한 설명은 다음 글에 적을 수 있도록 할게요.
오늘은 간략한 주석으로만.. :)
코드를 실행하면 이런 결과가 발생해요.
이전에 RestAPI로 모든 코인에 대한 정보를 단 한번의 응답으로 받았을 때에는 1초마다 요청하면, 1초마다 앱이 버벅였어요.
그렇지만 지금은 데이터를 계속 받는데도 앱이 버벅이지 않고 있어요.
나름 목적을 어느 정도 달성한 것 같아요.
앞으로는 다음의 작업들을 할 거에요.
1. connect와 send 함수에는 불필요한 역할이 포함되어 있어요.
private var codes = ""
변수의 값을 업데이트 하고 있는데, SOLID 원칙에도 맞지 않죠. 함수에는 한가지 기능만..
함수를 분리할 거에요.
2. 1번과 연결되어서 connect, send, close를 뷰와 뷰모델에서 자유자재로 컨트롤할 수 있어야 할 것 같아요.
지금은 메세지로 업비트 원화 마켓의 모든 코인을 요청하고 있지만, 만약 셀룰러 데이터를 사용한다면.. 개인적으로 너무 슬플 것 같아요.
괜찮을 방법 중 하나는 유저가 보는 코인을 알아내고 그에 맞춰서 메시지를 재요청할 수 있다면 좋을 것 같아요.
최적화는 정말 많은 연구가 필요할 것 같아요.. ㅜ ㅜ
3. 더 좋은 예외 처리 방법들을 찾아보고, ping 함수도 개선할 거에요.
등등
참고 자료:
GitHub - m0su/RxUpbit: RxSwift + 업비트 실시간 조회 구현
RxSwift + 업비트 실시간 조회 구현. Contribute to m0su/RxUpbit development by creating an account on GitHub.
github.com
GitHub - alfianlosari/CryptoTrackerMenuBar: A Realtime Crypto Tracker macOS Menu Bar App built with SwiftUI & WebSocket
A Realtime Crypto Tracker macOS Menu Bar App built with SwiftUI & WebSocket - GitHub - alfianlosari/CryptoTrackerMenuBar: A Realtime Crypto Tracker macOS Menu Bar App built with SwiftUI & W...
github.com
[iOS - swift] Starscream을 이용한 WebSockets (웹 소켓) 사용 방법
1. Starscream을 이용한 WebSockets (웹 소켓) 사용 방법 2. Starscream을 이용하여 WebSockets (웹 소켓) ping, pong 사용 방법 * URLSessionWebSocketTask를 이용하여 WebSocket 사용 방법은 이 포스팅 글 참고 WebSockets이란?
ios-development.tistory.com
'Project > SwiftUI 블록와이드' 카테고리의 다른 글
RestAPI vs WebSocketAPI. 현재 해결해야 할 문제 (0) | 2023.03.07 |
---|---|
블록와이드 1.1.4 업데이트 소식 (0) | 2023.02.27 |
[블록와이드] 뉴스 검색 기능 추가, 보완하고 싶은 점 (0) | 2023.02.14 |
[블록와이드] 뉴스 키워드 편집 기능 추가 (0) | 2023.02.11 |
[SwiftUI] @AppStorage로 배열(Array) 다루기 (0) | 2023.02.05 |
댓글