Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
198: WebSocket Communication in Swift
If you've been relying on standard REST APIs, you're used to the "request-response" dance: your app asks for data, the server sends it, and the connection closes. That works great for a user profile page, but it's a nightmare for something like a live sports score tracker. You can't have your app polling the server every second just to see if a goal was scored; it's a waste of battery and server resources. This is where WebSockets come in, allowing a persistent, two-way street between the client and the server.
The "One-and-Done" Trap
When I first started using URLSessionWebSocketTask, I fell into a common trap. I treated the receive method like a delegate or a notification listener. I'd call receive once, handle the incoming message in the completion handler, and assume the system would keep listening for the next one. It looked like this:
let webSocketTask = URLSession.shared.webSocketTask(with: url)
webSocketTask.resume()
webSocketTask.receive { result in
switch result {
case .success(let message):
print("Received score update: \(message)")
case .failure(let error):
print("Error: \(error)")
}
}
The problem? This code works exactly once. You'll get the first score update of the game, and then the connection will just sit there in silence. The receive method in Swift is designed to deliver a single message. Once that completion handler executes, the "listener" is gone. If you're building a real-time app, this is a critical failure—your users will see the first update and then think the app has frozen.
Closing the Loop for Continuous Streams
To actually maintain a stream, you have to implement a recursive listening pattern. I like to think of it as "re-arming the trap." The moment you finish processing a message, you must immediately tell the task to listen for the next one. This creates a continuous loop that keeps the data flowing without manually polling.
Here is how I usually structure this to ensure it doesn't crash or leak memory:
class ScoreTracker {
private var webSocketTask: URLSessionWebSocketTask?
func connect() {
let url = URL(string: "wss://sports-api.example.com/scores")!
webSocketTask = URLSession.shared.webSocketTask(with: url)
webSocketTask?.resume()
listen()
}
private func listen() {
webSocketTask?.receive { [weak self] result in
switch result {
case .success(let message):
self?.handleMessage(message)
// Re-arm the listener here
self?.listen()
case .failure(let error):
print("Connection lost: \(error)")
self?.handleDisconnect()
}
}
}
private func handleMessage(_ message: URLSessionWebSocketTask.Message) {
// Handle the actual data (string or binary)
}
private func handleDisconnect() {
// Implement exponential backoff retry logic here
}
}
Notice the [weak self]. Because the receive completion handler is escaping, you're creating a strong reference cycle if you aren't careful. If the user navigates away from the score screen and you don't have weak self, your ScoreTracker instance will live forever in memory, continuing to listen to a socket that no longer has a UI to update.
Managing Lifecycle and Silent Drops
There's a hidden cost to this approach: the "Silent Drop." WebSockets can feel connected when they aren't. A load balancer or a cellular tower might kill the TCP connection without sending a formal "close" frame. Your app thinks it's still listening, but no data is coming through.
To fight this, you need to implement a heartbeat (or "ping"). URLSessionWebSocketTask has a built-in sendPing method. I typically set up a timer that sends a ping every 30 seconds. If the ping fails or the server doesn't respond, you know the connection is dead and you can trigger your reconnection logic. It's an extra bit of boilerplate, but it's the difference between an app that feels professional and one that requires a hard restart every time the user switches from Wi-Fi to 5G.
📋 Practical Task
Build a Live Cryptocurrency Price Ticker
Your goal is to create a CryptoPriceListener class that connects to a public WebSocket API (such as the Binance Public WebSocket API) to track the price of Bitcoin (BTC) in real-time.
Requirements:
- Implement a recursive
receiveloop so the price updates continuously without stopping after the first message. - Use
[weak self]within the completion handler to prevent memory leaks. - Implement a
sendPing()` method that is called every 20 seconds via aTimerto keep the connection alive. - Handle the incoming JSON message to extract and print only the current price string to the console.
- Ensure the WebSocket task is properly cancelled in a
deinitmethod to clean up resources.
There are no comments for now.