Push notification ile 1-2 saniyelik gecikme kabul edilebilir. Ama chat, canlı skor, borsa takibi veya collaborative editing gibi uygulamalarda milisaniye önemli. WebSocket, HTTP'nin üzerine kurulu full-duplex iletişim protokolüdür — server ve client arasında sürekli açık bir kanal sağlar.
💡 Hızlı Not: iOS 13+ ile URLSessionWebSocketTask native WebSocket desteği geldi. Artık 3rd party kütüphane gerekmeden WebSocket kullanabilirsin.
İçindekiler
- WebSocket vs HTTP vs SSE
- URLSessionWebSocketTask
- Connection Management
- Message Protocol Tasarımı
- Chat Uygulaması
- Presence Sistemi
- Reconnection Stratejileri
- Combine ile WebSocket
- Scaling Considerations
- Best Practices
WebSocket vs HTTP vs SSE {#websocket-vs}
Özellik | HTTP | SSE | WebSocket |
|---|---|---|---|
Yön | Client → Server | Server → Client | İki yönlü |
Bağlantı | Her request yeni | Tek yönlü açık | İki yönlü açık |
Protokol | HTTP/1.1, 2, 3 | HTTP | ws:// / wss:// |
Overhead | Header her seferinde | Düşük | Çok düşük (2-6 byte frame) |
Kullanım | REST API | Notifications, feeds | Chat, gaming, collab |
URLSessionWebSocketTask {#urlsession-websocket}
swift
1class WebSocketManager: ObservableObject {2 private var webSocketTask: URLSessionWebSocketTask?3 @Published var messages: [ChatMessage] = []4 @Published var isConnected = false5 6 func connect() {7 let url = URL(string: "wss://api.example.com/ws")!8 webSocketTask = URLSession.shared.webSocketTask(with: url)9 webSocketTask?.resume()10 isConnected = true11 receiveMessage()12 }13 14 func disconnect() {15 webSocketTask?.cancel(with: .goingAway, reason: nil)16 isConnected = false17 }18 19 // Mesaj gönder20 func send(_ text: String) {21 let message = URLSessionWebSocketTask.Message.string(text)22 webSocketTask?.send(message) { error in23 if let error { print("Send error: \(error)") }24 }25 }26 27 // JSON mesaj gönder28 func sendJSON<T: Encodable>(_ object: T) throws {29 let data = try JSONEncoder().encode(object)30 let message = URLSessionWebSocketTask.Message.data(data)31 webSocketTask?.send(message) { error in32 if let error { print("Send error: \(error)") }33 }34 }35 36 // Mesaj al (recursive)37 private func receiveMessage() {38 webSocketTask?.receive { [weak self] result in39 switch result {40 case .success(let message):41 switch message {42 case .string(let text):43 DispatchQueue.main.async {44 self?.handleTextMessage(text)45 }46 case .data(let data):47 DispatchQueue.main.async {48 self?.handleDataMessage(data)49 }50 @unknown default:51 break52 }53 // Sonraki mesajı dinle54 self?.receiveMessage()55 case .failure(let error):56 print("Receive error: \(error)")57 self?.handleDisconnection()58 }59 }60 }61 62 private func handleTextMessage(_ text: String) {63 if let data = text.data(using: .utf8),64 let message = try? JSONDecoder().decode(ChatMessage.self, from: data) {65 messages.append(message)66 }67 }68 69 private func handleDataMessage(_ data: Data) {70 if let message = try? JSONDecoder().decode(ChatMessage.self, from: data) {71 messages.append(message)72 }73 }74}Connection Management {#connection}
swift
1// Ping/Pong heartbeat ile bağlantı canlılığı kontrol2func startHeartbeat() {3 Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in4 self?.webSocketTask?.sendPing { error in5 if let error {6 print("Ping failed: \(error)")7 self?.handleDisconnection()8 }9 }10 }11}Message Protocol Tasarımı {#protocol}
swift
1// Type-safe WebSocket message protocol2enum WSMessageType: String, Codable {3 case chat, typing, presence, read, system4}5 6struct WSMessage: Codable {7 let type: WSMessageType8 let payload: AnyCodable // veya generic9 10 // Chat message11 struct ChatPayload: Codable {12 let id: UUID13 let text: String14 let senderId: String15 let timestamp: Date16 }17 18 // Typing indicator19 struct TypingPayload: Codable {20 let userId: String21 let isTyping: Bool22 }23 24 // Presence25 struct PresencePayload: Codable {26 let userId: String27 let status: String // online, offline, away28 }29}Chat Uygulaması {#chat}
swift
1struct ChatView: View {2 @StateObject var wsManager = WebSocketManager()3 @State private var messageText = ""4 5 var body: some View {6 VStack {7 ScrollView {8 LazyVStack(alignment: .leading) {9 ForEach(wsManager.messages) { message in10 MessageBubble(message: message)11 }12 }13 }14 HStack {15 TextField("Mesaj yaz...", text: $messageText)16 .textFieldStyle(.roundedBorder)17 Button("Gönder") {18 wsManager.send(messageText)19 messageText = ""20 }21 }22 .padding()23 }24 .onAppear { wsManager.connect() }25 .onDisappear { wsManager.disconnect() }26 }27}Presence Sistemi {#presence}
Kullanıcının çevrimiçi/çevrimdışı durumunu takip et:
swift
1func updatePresence(status: String) {2 let presence = WSMessage(type: .presence, payload: PresencePayload(userId: currentUserId, status: status))3 try? sendJSON(presence)4}5 6// App lifecycle'da presence güncelle7NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: .main) { _ in8 updatePresence(status: "away")9}10NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main) { _ in11 updatePresence(status: "online")12}Reconnection Stratejileri {#reconnection}
swift
1// Exponential backoff ile otomatik reconnect2func handleDisconnection() {3 isConnected = false4 reconnect(attempt: 0)5}6 7private func reconnect(attempt: Int) {8 let maxAttempts = 109 guard attempt < maxAttempts else { return }10 11 let delay = min(pow(2.0, Double(attempt)), 60.0) // Max 60 saniye12 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in13 self?.connect()14 if self?.isConnected == false {15 self?.reconnect(attempt: attempt + 1)16 }17 }18}Combine ile WebSocket {#combine}
swift
1import Combine2 3class ReactiveWebSocket {4 private let messageSubject = PassthroughSubject<WSMessage, Error>()5 var messagePublisher: AnyPublisher<WSMessage, Error> { messageSubject.eraseToAnyPublisher() }6 7 // View'da kullanım8 // wsManager.messagePublisher9 // .filter { $0.type == .chat }10 // .receive(on: DispatchQueue.main)11 // .sink { message in /* UI güncelle */ }12}Scaling Considerations {#scaling}
Teknik | Açıklama |
|---|---|
Connection pooling | Tek WebSocket, multiplexed channels |
Message compression | Per-message deflate |
Binary protocol | JSON yerine Protobuf/MessagePack |
Load balancing | Sticky sessions veya pub/sub (Redis) |
Message Queue ve Offline Destek {#message-queue}
Baglanti koptugundan mesajlar kaybolmasin — local queue ile offline desteği:
swift
1class OfflineMessageQueue {2 private var pendingMessages: [WSMessage] = []3 private let storage = UserDefaults.standard4 private let queueKey = "ws_pending_messages"5 6 init() {7 // Onceki oturumdan kalan mesajlari yukle8 if let data = storage.data(forKey: queueKey),9 let messages = try? JSONDecoder().decode([WSMessage].self, from: data) {10 pendingMessages = messages11 }12 }13 14 func enqueue(_ message: WSMessage) {15 pendingMessages.append(message)16 persist()17 }18 19 func flushAll(via ws: WebSocketManager) {20 let messagesToSend = pendingMessages21 pendingMessages.removeAll()22 persist()23 24 for message in messagesToSend {25 try? ws.sendJSON(message)26 }27 }28 29 private func persist() {30 if let data = try? JSONEncoder().encode(pendingMessages) {31 storage.set(data, forKey: queueKey)32 }33 }34 35 var count: Int { pendingMessages.count }36}37 38// Kullanim39func send(_ text: String) {40 let message = WSMessage(type: .chat, text: text, timestamp: Date())41 if isConnected {42 try? sendJSON(message)43 } else {44 offlineQueue.enqueue(message)45 }46}47 48// Baglanti gelince kuyruktakileri gonder49func onReconnected() {50 offlineQueue.flushAll(via: self)51}WebSocket Guvenlik {#security}
Guvenlik Onlemi | Aciklama |
|---|---|
wss:// (TLS) | Tum trafik sifrelenir, MITM saldirisi engellenir |
Token auth | Handshake sirasinda JWT token gonder |
Rate limiting | Server tarafinda mesaj sayisi sinirlama |
Input validation | Gelen mesajlari parse etmeden once validate et |
Origin check | Server'da allowed origins kontrolu |
swift
1// Auth token ile WebSocket baglantisi2func connectWithAuth(token: String) {3 var request = URLRequest(url: URL(string: "wss://api.example.com/ws")!)4 request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")5 webSocketTask = URLSession.shared.webSocketTask(with: request)6 webSocketTask?.resume()7 isConnected = true8 receiveMessage()9}Best Practices {#best-practices}
- wss:// kullan — güvenlik için TLS zorunlu
- Heartbeat — 30 saniyede bir ping/pong
- Reconnection — exponential backoff ile
- Message queue — offline mesajları queue'la
- Background — URLSession background task
- Auth token yenileme — token expire oldugunda reconnect ile yeni token gonder
- Message deduplication — ayni mesajin birden fazla islenmesini UUID ile onle
- Binary protocol — yuksek trafikte JSON yerine Protobuf veya MessagePack kullan
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var: **Kaynaklar:** - [Apple: URLSessionWebSocketTask](https://developer.apple.com/documentation/foundation/urlsessionwebsockettask) - [RFC 6455: The WebSocket Protocol](https://tools.ietf.org/html/rfc6455) - [Socket.IO](https://socket.io/)

