OddSockets Swift SDK
Official Swift SDK for OddSockets real-time messaging platform
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
// 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
# Podfile
pod 'OddSockets', '~> 1.0'
# Cartfile
github "oddsockets/swift-sdk" ~> 1.0
Quick Start
Basic Usage
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
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
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
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
// 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:
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
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
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
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
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)")
}
}
}