Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- swiftUI
- UITextView
- 리펙터링
- combine
- swift documentation
- SWIFT
- Observable
- 애니메이션
- 리팩토링
- ios
- RxCocoa
- uitableview
- MVVM
- 스위프트
- uiscrollview
- Human interface guide
- Xcode
- Protocol
- Clean Code
- 클린 코드
- HIG
- rxswift
- 리펙토링
- collectionview
- UICollectionView
- ribs
- tableView
- clean architecture
- map
- Refactoring
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] WebSocket 사용 방법 (웹 소켓, URLSessionWebSocketTask, URLSessionWebSocketDelegate) 본문
iOS 응용 (swift)
[iOS - swift] WebSocket 사용 방법 (웹 소켓, URLSessionWebSocketTask, URLSessionWebSocketDelegate)
jake-kim 2022. 9. 10. 23:11* StarScream을 통해 WebSocket 간단하게 사용 방법은 이전 포스팅 글 참고
WebSockets이란?
- 클라이언트와 서버 사이의 동적인 양방향 연결 채널(Socket Connection)을 구성
- WebSockets API를 통해 서버로 메세지를 보내면, 별다른 API 요청 없이 응답을 수신
- HTTP 통신 방법 vs WebSocket 통신 방법
- WebSockets 프로토콜: 접속에만 HTTP를 사용하고 그 후 통신은 WebSockets 독자적인 프로토콜을 사용
- WebSockets은 header가 작기 때문에 overhead가 적은 장점이 존재
- ex) slack의 실시간 채팅, 금융앱에서 실시간 주가 현황
WebSocket 구현
- 싱글톤으로 구현하기 위해 shared를 선언하고 url을 외부에서 잘못입력하여 사용할 경우 throw를 던져주기 위해 WebSocketError라는 타입을 정의
- WebSocket의 delegate는 NSObject타입이어야 하므로, NSObject를 서브클래싱
import Foundation
enum WebSocketError: Error {
case invalidURL
}
final class WebSocket: NSObject {
static let shared = WebSocket()
private override init() {}
}
- 외부에서 접근하는 프로퍼티 정의
- onReceiveClosure는 웹소켓으로부터 값을 받은 경우 처리를 위해 선언
- delegate는 외부에서 접근하여 open, close되었을때 이벤트를 처리하기 위함
var url: URL?
var onReceiveClosure: ((String?, Data?) -> ())?
weak var delegate: URLSessionWebSocketDelegate?
...
// delegate 설정은 URLSession을 만들때 설정
let urlSession = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
...
extension WebSocket: URLSessionWebSocketDelegate {
func urlSession(
_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didOpenWithProtocol protocol: String?
) {
self.delegate?.urlSession?(
session,
webSocketTask: webSocketTask,
didOpenWithProtocol: `protocol`
)
}
func urlSession(
_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?
) {
self.delegate?.urlSession?(
session,
webSocketTask: webSocketTask,
didCloseWith: closeCode,
reason: reason
)
}
}
- 내부적으로 사용하는 프로퍼티 선언
private var webSocketTask: URLSessionWebSocketTask? {
didSet { oldValue?.cancel(with: .goingAway, reason: nil) }
}
private var timer: Timer?
- 웹소켓을 여는 메소드 정의
- url을 잘못 입력한 경우에는 throw
- 서버에 의해 연결이 끊어지지 않도록 주기적으로 ping을 서버에 보내주는 작업도 추가
(webSocketTask.sendPing메소드가 내부적으로 존재하는것 사용)
func openWebSocket() throws {
guard let url = url else { throw WebSocketError.invalidURL }
let urlSession = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
let webSocketTask = urlSession.webSocketTask(with: url)
webSocketTask.resume()
self.webSocketTask = webSocketTask
self.startPing()
}
private func startPing() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(
withTimeInterval: 10,
repeats: true,
block: { [weak self] _ in self?.ping() }
)
}
private func ping() {
self.webSocketTask?.sendPing(pongReceiveHandler: { [weak self] error in
guard let error = error else { return }
print("Ping failed \(error)")
self?.startPing()
})
}
- send 메소드 정의
- URLSessionWebSocketTask의 데이터 전송, 수신 타입은 2가지가 존재
// 내부적으로 정의된 2가지 타입
public enum URLSessionWebSocketTask.Message {
case data(Data)
case string(String)
}
- 2가지 타입으로 메시지를 주고받기때문에 send 메소드는 message, data 두 가지로 분류
- 메시지를 만들어서, webSocketTask.send(_:completionHandler:)로 전송
func send(message: String) {
self.send(message: message, data: nil)
}
func send(data: Data) {
self.send(message: nil, data: data)
}
private func send(message: String?, data: Data?) {
let taskMessage: URLSessionWebSocketTask.Message
if let string = message {
taskMessage = URLSessionWebSocketTask.Message.string(string)
} else if let data = data {
taskMessage = URLSessionWebSocketTask.Message.data(data)
} else {
return
}
print("Send message \(taskMessage)")
self.webSocketTask?.send(taskMessage, completionHandler: { error in
guard let error = error else { return }
print("WebSOcket sending error: \(error)")
})
}
- 연결을 종료시키는 closeWebSocket() 메소드 구현
func closeWebSocket() {
self.webSocketTask = nil
self.onReceiveClosure = nil
self.timer?.invalidate()
self.delegate = nil
}
// 위에서 didSet에 cancel()되게끔 해놓았으므로, webSocketTask = nil 하면 cancel도 실행
private var webSocketTask: URLSessionWebSocketTask? {
didSet { oldValue?.cancel(with: .goingAway, reason: nil) }
}
사용하는쪽
- WebSocket.shared로 접근하여 사용
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
WebSocket.shared.url = URL(string: "ws://localhost:1337/")
try? WebSocket.shared.openWebSocket()
WebSocket.shared.delegate = self
WebSocket.shared.onReceiveClosure = { (string, data) in
print(string, data)
}
WebSocket.shared.send(message: "hello world")
}
}
extension ViewController: URLSessionWebSocketDelegate {
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
print("open")
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
print("close")
}
}
테스트를 위한 웹소켓 서버 생성
- 이 포스팅 글에서 사용한 node 웹 서버 오픈 (5분정도 소요)
- node chat-server.js까지 실행하여 준비
- 위에서 구현한 코드를 실행하면, hello world가 웹소켓에 찍히는 것을 확인
WebSocket.shared.send(message: "hello world")
* 전체 코드: https://github.com/JK0369/ExURLSessionWebSocketTask
* 참고
https://appspector.com/blog/websockets-in-ios-using-urlsessionwebsockettask
'iOS 응용 (swift)' 카테고리의 다른 글
Comments