In this post, I’ll share how to build socket application.

Project Structure

I created a workspace and put the Client (SwiftUI) and Server (Vapor)

Server (Vapor)

Server code is very simple. It is only 32 lines of code.

import Vapor

func routes(_ app: Application) throws {
    //Endpoint - ws://127.0.0.1:8080/echo
    app.webSocket("echo") { req, ws in
        // Connected WebSocket.
        
        //Send Ping to Client
        ws.sendPing()
        ws.onPong { ws, data in
            // Pong was received from client
            print("Connection alive, Received Pong")
        }
        
        ws.onText { ws, text in
            // String received by this WebSocket.
            ws.send("👋 Sent Message: \(text) from Server")
        }

        ws.onBinary { ws, binary in
            // [UInt8] received by this WebSocket.
            print(binary)
            ws.send("Received Data: \(binary.readableBytes)")
        }
        
        ws.onPing { ws, data in
            // Ping was received.
            print("Received Ping")
        }
    }
}

Client (SwiftUI)

I used URLSessionWebSocketTask.

Here is viewModel logic. This class is @Observale and inherent from NSObject to handle URLSessionWebSocketDelegate.

import SwiftUI
import Foundation

@Observable class ContentViewModel: NSObject {
    var message: String = ""
    var received: String = ""
    private var session: URLSession!
    private var socketTask: URLSessionWebSocketTask!
    
    override init() {
        super.init()
        self.session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        self.socketTask = session.webSocketTask(
            with: URL(string: "ws://127.0.0.1:8080/echo")!
        )
        self.socketTask.resume()
    }
    
    func ping() {
        socketTask.sendPing { error in
            if let error {
                print("ping failed: \(error.localizedDescription)")
            }
            else {
                print("pong received")
            }
        }
    }
    
    func send() async {
        try? await socketTask.send(.string(message))
    }
    
    func receive() async {
        while let message = try? await socketTask.receive() {
            switch message {
            case let .string(receivedMessage):
                print("Received Message from Server: \(receivedMessage)")
                received = receivedMessage
            default:
                break
            }
        }
    }
    
    func disconnect() {
        socketTask.cancel(with: .goingAway, reason: nil)
    }
}

extension ContentViewModel: URLSessionWebSocketDelegate {
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        print("Connected")
        Task {
            await receive()
        }
    }

    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        print("Disconnected: \(closeCode)")
    }
}

SwiftUI Code

import SwiftUI

struct ContentView: View {
    @State private var viewModel = ContentViewModel()
    var body: some View {
        VStack {
            Text(viewModel.received)
            Divider()
            HStack {
                TextField(text: $viewModel.message) {
                    Text("Message")
                }
                Button("Send") {
                    Task {
                        await viewModel.send()
                    }
                }.padding(4)
                    .background(.blue)
                    .foregroundColor(.white)
            }
            Divider()
            Button("Ping") {
                viewModel.ping()
            }.frame(
                maxWidth: .infinity,
                maxHeight: 30
            ).background(.teal)
                .foregroundColor(.white)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

Let’s run a simple socket application

Both client and server can send a ping. When received the ping message then receiver send a pong message automatically. It’s a way to check status of connections.

If you want to more details about WebSocket, check my post

Leave a comment

Quote of the week

"People ask me what I do in the winter when there's no baseball. I'll tell you what I do. I stare out the window and wait for spring."

~ Rogers Hornsby