일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- tableView
- 리펙터링
- 스위프트
- HIG
- uiscrollview
- 리팩토링
- uitableview
- swiftUI
- Observable
- clean architecture
- swift documentation
- 리펙토링
- MVVM
- combine
- UITextView
- Clean Code
- Human interface guide
- ribs
- collectionview
- rxswift
- map
- 애니메이션
- ios
- Xcode
- RxCocoa
- Protocol
- 클린 코드
- UICollectionView
- Refactoring
- SWIFT
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Starscream을 이용한 WebSockets (웹 소켓) 사용 방법 본문
1. Starscream을 이용한 WebSockets (웹 소켓) 사용 방법
2. Starscream을 이용하여 WebSockets (웹 소켓) ping, pong 사용 방법
* URLSessionWebSocketTask를 이용하여 WebSocket 사용 방법은 이 포스팅 글 참고
WebSockets이란?
- 클라이언트와 서버 사이의 동적인 양방향 연결 채널(Socket Connection)을 구성
- WebSockets API를 통해 서버로 메세지를 보내면, 별다른 API 요청 없이 응답을 수신
- HTTP 통신 방법 vs WebSocket 통신 방법
- WebSockets 프로토콜: 접속에만 HTTP를 사용하고 그 후 통신은 WebSockets 독자적인 프로토콜을 사용
- WebSockets은 header가 작기 때문에 overhead가 적은 장점이 존재
- ex) slack의 실시간 채팅, 금융앱에서 실시간 주가 현황
cf) polling 방식: 클라이언트에서 실시간 업데이트를 위해서 서버에 API 요청을 몇초마다 요청하여 결과값을 얻는 방식
(WebSockets보다 데이터 갱신이 느린 단점 존재)
Starscream 프레임워크
- iOS내부적으로 제공하는 URLSessionWebSocketTask을 사용하면 웹 소켓을 사용하면 어렵지만, Starscream을 이용하여 웹 소켓을 심플하게 준비하고 사용이 가능
- Starscream은 내부적으로 GCD를 이용하여 모두 background에서 동작하도록 설계 (NonBlocking)
pod 'Starscream'
pod 'SnapKit' # UI 작업 편리를 위해 사용
WebSockets 테스트를 위해 서버 준비
- nodejs를 통해 local에 서버 동작 - node가 설치 안되어 있다면, 설치
- node가 설치되어 있는지 확인
node --version
- node 설치 (homebrew를 통해 설치)
brew install node
- node가 설치되어 있는지 확인
- npm을 통해 chat서버 구축
- js파일 준비 - 이곳에서 코드 다운로드 (출처 - raywenderlich)
- 압축 해제 후, cd/EmojiTransmitter-starter/nodeapp
- websocket 다운로드
npm install websocket
- 채팅 서버 시작
# cd nodeapp node chat-server.js
- safari나 chrome에서 frontend.html 오픈
- 채팅기능 확인
- 위와 같이 준비된 상태라면, 해당 서버와 연결된 유저에게 broadcast로 전달
Starscream 사용방법
예제 프로젝트)
이름 입력 | 서버에 이모지 전송 |
- 이모지 CollectionView가 들어있는 ViewController에서 webSocket 연동
"EmojiViewController.swift" 파일에 아래 부분 모두 작성
1. import
import Starscream
2. web secket 초기화
private var socket: WebSocket?
// viewDidLoad에서 호출
private func setupWebSocket() {
let url = URL(string: "ws://localhost:1337/")!
var request = URLRequest(url: url)
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket?.delegate = self
socket?.connect()
}
delegate 준수 (.conected에 client.write로 이름 입력)
extension EmojiViewController: WebSocketDelegate {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
client.write(string: userName)
print("websocket is connected: \(headers)")
case .disconnected(let reason, let code):
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let text):
print("received text: \(text)")
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
print("websocket is canclled")
case .error(let error):
print("websocket is error = \(error!)")
}
}
}
3. message 전송
private func sendMessage(_ message: String) {
self.title = "메세지 전송"
socket?.write(string: message)
}
-> 버튼을 탭한 경우 위 메소드호출
@objc private func didTapButton() {
self.sendMessage(self.informationLabelText)
}
4. 메세지 수신
private func receivedMessage(_ message: String, senderName: String) {
self.title = "메세지 from (\(senderName))"
self.informationLabelText = message
}
-> WebSocketDelegate구현 부의 .text 케이스에서 receive 처리
case .text(let text):
// 4-2
guard let data = text.data(using: .utf16),
let jsonData = try? JSONSerialization.jsonObject(with: data, options: []),
let jsonDict = jsonData as? NSDictionary,
let messageType = jsonDict["type"] as? String else {
return
}
if messageType == "message",
let messageData = jsonDict["data"] as? NSDictionary,
let messageAuthor = messageData["author"] as? String,
let messageText = messageData["text"] as? String {
self.receivedMessage(messageText, senderName: messageAuthor)
}
5. connection 해제
deinit {
socket?.disconnect()
socket?.delegate = nil
}
완성
* WebSocket이 연동된 EmojiViewController 전체 코드
//
// EmojiViewController.swift
// ExWebSockets
//
// Created by Jake.K on 2022/01/07.
//
import UIKit
// 1
import Starscream
final class EmojiViewController: UIViewController {
// MARK: Constants
private enum Metric {
static let collectionViewItemSize = CGSize(width: 40, height: 40)
static let collectionViewSpacing = 16.0
static let collectionViewContentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
}
private enum Color {
static let white = UIColor.white
static let clear = UIColor.clear
}
// MARK: UI
private let informationLabel: UILabel = {
let label = UILabel()
label.textColor = .black
return label
}()
private let sendButton: UIButton = {
let button = UIButton()
button.setTitle("이모지 전송", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
button.setTitleColor(.blue, for: .highlighted)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
return button
}()
private let separatorView: UIView = {
let view = UIView()
view.backgroundColor = .lightGray
return view
}()
// MARK: Priperties
private let emojis = ["😀", "😬", "😁", "😂", "😃", "😄", "😅", "😆", "😇", "😉", "😊", "🙂", "🙃", "☺️", "😋", "😌", "😍", "😘", "😗", "😙", "😚", "😜", "😝", "😛", "🤑", "🤓", "😎", "🤗", "😏", "😶", "😐", "😑", "😒", "🙄", "🤔", "😳", "😞", "😟", "😠", "😡", "😔", "😕", "🙁", "☹️", "😣", "😖", "😫", "😩", "😤", "😮", "😱", "😨", "😰", "😯", "😦", "😧", "😢", "😥", "😪", "😓", "😭", "😵", "😲", "🤐", "😷", "🤒", "🤕", "😴", "💩"]
private let collectionView: UICollectionView = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = Metric.collectionViewItemSize
flowLayout.minimumInteritemSpacing = Metric.collectionViewSpacing
flowLayout.minimumLineSpacing = Metric.collectionViewSpacing
let view = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
view.register(EmojiCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
view.contentInset = Metric.collectionViewContentInset
view.showsHorizontalScrollIndicator = false
view.backgroundColor = Color.clear
return view
}()
private var informationLabelText: String = "" {
didSet {
self.informationLabel.attributedText = NSMutableAttributedString()
.resize(string: self.informationLabelText, fontSize: 120)
}
}
private let userName: String
private var socket: WebSocket?
init() {
self.userName = UserDefaults.standard.string(forKey: "name") ?? ""
super.init(nibName: nil, bundle: nil)
}
// 5
deinit {
socket?.disconnect()
socket?.delegate = nil
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError()
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "이모지 선택"
self.view.backgroundColor = Color.white
self.view.addSubview(self.collectionView)
self.view.addSubview(self.informationLabel)
self.view.addSubview(self.sendButton)
self.view.addSubview(self.separatorView)
self.collectionView.snp.makeConstraints {
$0.left.right.equalToSuperview()
$0.top.equalTo(self.view.safeAreaLayoutGuide).inset(250)
$0.bottom.equalTo(self.view.safeAreaLayoutGuide)
}
self.informationLabel.snp.makeConstraints {
$0.top.equalTo(self.view.safeAreaLayoutGuide).inset(32)
$0.centerX.equalToSuperview()
}
self.sendButton.snp.makeConstraints {
$0.top.equalTo(self.collectionView.snp.top).offset(-50)
$0.centerX.equalToSuperview()
}
self.separatorView.snp.makeConstraints {
$0.height.equalTo(1)
$0.bottom.equalTo(self.collectionView.snp.top).offset(1)
$0.left.right.equalToSuperview()
}
self.collectionView.dataSource = self
self.collectionView.delegate = self
// 2 connect to web socket server
self.setupWebSocket()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.view.endEditing(true)
}
private func setupWebSocket() {
let url = URL(string: "ws://localhost:1337/")!
var request = URLRequest(url: url)
request.timeoutInterval = 5
socket = WebSocket(request: request)
socket?.delegate = self
socket?.connect()
}
@objc private func didTapButton() {
// 3-2
self.sendMessage(self.informationLabelText)
}
// 3-1
private func sendMessage(_ message: String) {
self.title = "메세지 전송"
socket?.write(string: message)
}
// 4-1
private func receivedMessage(_ message: String, senderName: String) {
self.title = "메세지 from (\(senderName))"
self.informationLabelText = message
}
}
extension EmojiViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.emojis.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! EmojiCollectionViewCell
cell.prepare(emoji: emojis[indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let message = emojis[indexPath.item]
self.informationLabelText = message
}
}
extension EmojiViewController: WebSocketDelegate {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
client.write(string: userName)
print("websocket is connected: \(headers)")
case .disconnected(let reason, let code):
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let text):
// 4-2
guard let data = text.data(using: .utf16),
let jsonData = try? JSONSerialization.jsonObject(with: data, options: []),
let jsonDict = jsonData as? NSDictionary,
let messageType = jsonDict["type"] as? String else {
return
}
if messageType == "message",
let messageData = jsonDict["data"] as? NSDictionary,
let messageAuthor = messageData["author"] as? String,
let messageText = messageData["text"] as? String {
self.receivedMessage(messageText, senderName: messageAuthor)
}
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
print("websocket is canclled")
case .error(let error):
print("websocket is error = \(error!)")
}
}
}
* 전체 코드: https://github.com/JK0369/ExWebSocket
* 참고
https://www.raywenderlich.com/861-websockets-on-ios-with-starscream
https://github.com/daltoniam/Starscream
https://www.raywenderlich.com/861-websockets-on-ios-with-starscream