OddSockets Swift SDK

Official Swift SDK for OddSockets real-time messaging platform

Swift 5.7+ Swift Package Manager iOS 15+ High Performance Async/Await

Overview & Features

The OddSockets Swift SDK provides a modern, native interface for real-time messaging in iOS, macOS, watchOS, and tvOS applications, built with Swift's latest async/await patterns and Combine framework.

Modern Swift

Built with Swift 5.7+ featuring async/await, actors, and modern concurrency patterns.

Multi-Platform

Supports iOS 15+, macOS 12+, watchOS 8+, and tvOS 15+ with unified API.

SwiftUI Ready

ObservableObject conformance and @Published properties for seamless SwiftUI integration.

High Performance

Optimized for low latency with efficient WebSocket connections and smart routing.

Cost Effective

No per-message pricing, industry-standard 32KB message limits, transparent pricing.

Automatic Failover

Built-in redundancy and intelligent error handling for 99.9% uptime.

Installation

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/oddsockets/swift-sdk.git", from: "1.0.0")
]

Or add via Xcode: File → Add Package Dependencies → Enter URL

ruby
# Podfile
pod 'OddSockets', '~> 1.0'
text
# Cartfile
github "oddsockets/swift-sdk" ~> 1.0

Quick Start

Basic Usage

swift
import OddSockets

// Create configuration
let config = OddSocketsConfig.Builder()
    .apiKey("ak_live_1234567890abcdef")
    .build()

// Initialize client
let client = OddSockets(config: config)

// Get channel
let channel = client.channel("my-channel")

// Subscribe to messages
try await channel.subscribe { message in
    print("Received: \(message.data)")
}

// Publish a message
try await channel.publish("Hello, World!")

SwiftUI Integration

swift
import SwiftUI
import OddSockets

struct ChatView: View {
    @StateObject private var channel: OddSocketsChannel
    @State private var messageText = ""
    
    init() {
        let config = OddSocketsConfig.Builder()
            .apiKey("ak_live_1234567890abcdef")
            .build()
        let client = OddSockets(config: config)
        _channel = StateObject(wrappedValue: client.channel("chat"))
    }
    
    var body: some View {
        VStack {
            List(channel.messageHistory, id: \.id) { message in
                Text(message.data?.description ?? "")
            }
            
            HStack {
                TextField("Message", text: $messageText)
                Button("Send") {
                    Task {
                        try await channel.publish(messageText)
                        messageText = ""
                    }
                }
            }
        }
        .task {
            try? await channel.subscribe { message in
                // Messages automatically update via @Published
            }
        }
    }
}

Combine Integration

swift
import Combine
import OddSockets

class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    private var cancellables = Set()
    private let channel: OddSocketsChannel
    
    init() {
        let config = OddSocketsConfig.Builder()
            .apiKey("ak_live_1234567890abcdef")
            .build()
        let client = OddSockets(config: config)
        channel = client.channel("chat")
        
        // Subscribe to message publisher
        channel.messagePublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] message in
                self?.messages.append(message)
            }
            .store(in: &cancellables)
    }
    
    func sendMessage(_ text: String) async {
        try? await channel.publish(text)
    }
}

Configuration

Client Configuration

swift
let config = OddSocketsConfig.Builder()
    .apiKey("ak_live_1234567890abcdef")     // Required: Your OddSockets API key
    .userId("user-123")                     // Optional: User identifier
    .autoConnect(true)                      // Optional: Auto-connect on creation
    .reconnectAttempts(5)                   // Optional: Max reconnection attempts
    .heartbeatInterval(30)                  // Optional: Heartbeat interval (seconds)
    .timeout(10)                            // Optional: Connection timeout (seconds)
    .build()

Channel Options

swift
// Subscribe with options
let subscribeOptions = SubscribeOptions.Builder()
    .enablePresence(true)                   // Enable presence tracking
    .retainHistory(true)                    // Retain message history
    .filterExpression("user.premium == true") // Message filter expression
    .build()

try await channel.subscribe(handler: { message in
    print("Received: \(message)")
}, options: subscribeOptions)

// Publish with options
let publishOptions = PublishOptions.Builder()
    .ttl(3600)                              // Time to live (seconds)
    .metadata(["priority": "high"])         // Additional metadata
    .storeInHistory(true)                   // Store in message history
    .build()

try await channel.publish(message, options: publishOptions)

Examples

Explore comprehensive examples demonstrating the OddSockets Swift SDK in production applications:

Performance & Compatibility

OddSockets Swift SDK delivers superior performance with broad Apple platform compatibility:

<50ms
Latency
99.9%
Uptime
32KB
Max Message
1M+
Messages/sec

Platform Support

  • iOS 15.0+
  • macOS 12.0+
  • watchOS 8.0+
  • tvOS 15.0+

Swift Features

  • Swift 5.7+
  • Async/Await
  • Combine Framework
  • SwiftUI Ready

Framework Integrations

The OddSockets Swift SDK works seamlessly with all Apple frameworks. Here are examples showing how to integrate with popular patterns:

SwiftUI

swift
import SwiftUI
import OddSockets

struct ContentView: View {
    @StateObject private var viewModel = ChatViewModel()
    @State private var messageText = ""
    
    var body: some View {
        NavigationView {
            VStack {
                List(viewModel.messages, id: \.id) { message in
                    MessageRow(message: message)
                }
                
                HStack {
                    TextField("Type a message...", text: $messageText)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    Button("Send") {
                        Task {
                            await viewModel.sendMessage(messageText)
                            messageText = ""
                        }
                    }
                    .disabled(messageText.isEmpty)
                }
                .padding()
            }
            .navigationTitle("Chat")
        }
        .task {
            await viewModel.connect()
        }
    }
}

@MainActor
class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    @Published var isConnected = false
    
    private let client: OddSockets
    private let channel: OddSocketsChannel
    
    init() {
        let config = OddSocketsConfig.Builder()
            .apiKey("ak_live_1234567890abcdef")
            .build()
        
        client = OddSockets(config: config)
        channel = client.channel("swiftui-chat")
    }
    
    func connect() async {
        do {
            try await channel.subscribe { [weak self] message in
                await MainActor.run {
                    self?.messages.append(message)
                }
            }
            isConnected = true
        } catch {
            print("Connection failed: \(error)")
        }
    }
    
    func sendMessage(_ text: String) async {
        do {
            try await channel.publish(text)
        } catch {
            print("Send failed: \(error)")
        }
    }
}

UIKit

swift
import UIKit
import OddSockets
import Combine

class ChatViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var messageTextField: UITextField!
    @IBOutlet weak var sendButton: UIButton!
    
    private var messages: [Message] = []
    private var cancellables = Set()
    private let client: OddSockets
    private let channel: OddSocketsChannel
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        let config = OddSocketsConfig.Builder()
            .apiKey("ak_live_1234567890abcdef")
            .build()
        
        client = OddSockets(config: config)
        channel = client.channel("uikit-chat")
        
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        connectToChannel()
    }
    
    private func setupUI() {
        tableView.dataSource = self
        tableView.delegate = self
        
        sendButton.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
    }
    
    private func connectToChannel() {
        // Subscribe to messages using Combine
        channel.messagePublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] message in
                self?.messages.append(message)
                self?.tableView.reloadData()
                self?.scrollToBottom()
            }
            .store(in: &cancellables)
        
        // Connect to channel
        Task {
            do {
                try await channel.subscribe { _ in
                    // Messages handled by Combine publisher
                }
            } catch {
                await MainActor.run {
                    self.showError("Connection failed: \(error)")
                }
            }
        }
    }
    
    @objc private func sendButtonTapped() {
        guard let text = messageTextField.text, !text.isEmpty else { return }
        
        Task {
            do {
                try await channel.publish(text)
                await MainActor.run {
                    self.messageTextField.text = ""
                }
            } catch {
                await MainActor.run {
                    self.showError("Send failed: \(error)")
                }
            }
        }
    }
    
    private func scrollToBottom() {
        guard !messages.isEmpty else { return }
        let indexPath = IndexPath(row: messages.count - 1, section: 0)
        tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
    
    private func showError(_ message: String) {
        let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

Combine

swift
import Combine
import OddSockets

class ChatService: ObservableObject {
    @Published var messages: [Message] = []
    @Published var connectionStatus: ConnectionStatus = .disconnected
    @Published var presenceInfo: PresenceInfo?
    
    private let client: OddSockets
    private let channel: OddSocketsChannel
    private var cancellables = Set()
    
    enum ConnectionStatus {
        case disconnected, connecting, connected, error(String)
    }
    
    init(apiKey: String, channelName: String) {
        let config = OddSocketsConfig.Builder()
            .apiKey(apiKey)
            .build()
        
        client = OddSockets(config: config)
        channel = client.channel(channelName)
        
        setupPublishers()
    }
    
    private func setupPublishers() {
        // Message publisher
        channel.messagePublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] message in
                self?.messages.append(message)
            }
            .store(in: &cancellables)
        
        // Presence publisher
        channel.presencePublisher
            .receive(on: DispatchQueue.main)
            .assign(to: \.presenceInfo, on: self)
            .store(in: &cancellables)
        
        // Event publisher for connection status
        channel.eventPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] (eventType, data) in
                switch eventType {
                case .connected:
                    self?.connectionStatus = .connected
                case .disconnected:
                    self?.connectionStatus = .disconnected
                default:
                    break
                }
            }
            .store(in: &cancellables)
    }
    
    func connect() -> AnyPublisher {
        connectionStatus = .connecting
        
        return Future { [weak self] promise in
            guard let self = self else {
                promise(.failure(OddSocketsError.clientError("Service deallocated")))
                return
            }
            
            Task {
                do {
                    try await self.channel.subscribe { _ in
                        // Messages handled by publisher
                    }
                    promise(.success(()))
                } catch {
                    await MainActor.run {
                        self.connectionStatus = .error(error.localizedDescription)
                    }
                    promise(.failure(error))
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    func sendMessage(_ text: String) -> AnyPublisher {
        return Future { [weak self] promise in
            guard let self = self else {
                promise(.failure(OddSocketsError.clientError("Service deallocated")))
                return
            }
            
            Task {
                do {
                    try await self.channel.publish(text)
                    promise(.success(()))
                } catch {
                    promise(.failure(error))
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    func disconnect() {
        Task {
            await channel.unsubscribe()
            await MainActor.run {
                self.connectionStatus = .disconnected
            }
        }
    }
}

watchOS

swift
import SwiftUI
import WatchKit
import OddSockets

struct WatchChatView: View {
    @StateObject private var viewModel = WatchChatViewModel()
    @State private var isShowingMessageInput = false
    
    var body: some View {
        NavigationView {
            VStack {
                if viewModel.messages.isEmpty {
                    Text("No messages yet")
                        .foregroundColor(.secondary)
                } else {
                    List(viewModel.messages.suffix(10), id: \.id) { message in
                        VStack(alignment: .leading, spacing: 2) {
                            Text(message.data?.description ?? "")
                                .font(.caption)
                            Text(message.timestamp, style: .time)
                                .font(.caption2)
                                .foregroundColor(.secondary)
                        }
                    }
                }
                
                Button("Send Message") {
                    isShowingMessageInput = true
                }
                .buttonStyle(.borderedProminent)
            }
            .navigationTitle("Chat")
            .navigationBarTitleDisplayMode(.inline)
        }
        .sheet(isPresented: $isShowingMessageInput) {
            MessageInputView { message in
                Task {
                    await viewModel.sendMessage(message)
                }
            }
        }
        .task {
            await viewModel.connect()
        }
    }
}

@MainActor
class WatchChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    @Published var isConnected = false
    
    private let client: OddSockets
    private let channel: OddSocketsChannel
    
    init() {
        let config = OddSocketsConfig.Builder()
            .apiKey("ak_live_1234567890abcdef")
            .build()
        
        client = OddSockets(config: config)
        channel = client.channel("watch-chat")
    }
    
    func connect() async {
        do {
            try await channel.subscribe { [weak self] message in
                await MainActor.run {
                    self?.messages.append(message)
                    // Keep only last 20 messages for memory efficiency
                    if let messages = self?.messages, messages.count > 20 {
                        self?.messages = Array(messages.suffix(20))
                    }
                }
            }
            isConnected = true
        } catch {
            print("Watch connection failed: \(error)")
        }
    }
    
    func sendMessage(_ text: String) async {
        do {
            try await channel.publish(text)
        } catch {
            print("Watch send failed: \(error)")
        }
    }
}