Rate Limiting in Swift: A Complete Guide for iOS and macOS Applications
Rate limiting is a critical mechanism for the stable operation of mobile and desktop applications. In the Swift ecosystem (iOS/macOS), this approach allows you to control the number of API calls, I/O operations, or any repetitive actions per unit of time. Without rate limiting, your application risks being blocked by the server, wasting battery life on endless retries, or creating excessive network load.
Why is request frequency control needed in Swift?
- Protection against exceeding API limits — many servers (e.g., GitHub, Twitter, Stripe) set strict quotas on the number of requests per minute/hour.
- Performance optimization — preventing a "storm" of calls during fast scrolling or animations.
- Saving traffic and battery — especially relevant for mobile devices.
- Compliance with App Store policies — excessive background requests can lead to application rejection.
Basic approaches to implementing Rate Limiting in Swift
Let's look at three basic patterns that can be implemented without third-party libraries:
1. Token Bucket
A classic algorithm where "tokens" are added at a fixed rate. Each request consumes one token. If tokens run out, the request is rejected.
import Foundation
final class TokenBucket { private let capacity: Int private var tokens: Int private let refillRate: Double // tokens per second private var lastRefill: Date private let queue = DispatchQueue(label: "token.bucket.queue") init(capacity: Int, refillRate: Double) { self.capacity = capacity self.tokens = capacity self.refillRate = refillRate self.lastRefill = Date() } func tryConsume() -> Bool { queue.sync { refill() if tokens > 0 { tokens -= 1 return true } return false } } private func refill() { let now = Date() let elapsed = now.timeIntervalSince(lastRefill) let newTokens = Int(elapsed * refillRate) if newTokens > 0 { tokens = min(capacity, tokens + newTokens) lastRefill = now } }}
// Usage examplelet limiter = TokenBucket(capacity: 10, refillRate: 2.0) // 10 requests, 2 per secondif limiter.tryConsume() { print("✅ Request allowed")} else { print("⛔️ Limit exceeded, wait")}2. Sliding Window Algorithm
Stores timestamps of the last N successful requests. If the window is full, it blocks new calls.
final class SlidingWindowLimiter { private let maxRequests: Int private let windowDuration: TimeInterval private var timestamps: [Date] = [] private let queue = DispatchQueue(label: "sliding.window.queue") init(maxRequests: Int, windowDuration: TimeInterval = 60) { self.maxRequests = maxRequests self.windowDuration = windowDuration } func canProceed() -> Bool { queue.sync { let now = Date() // Remove outdated timestamps timestamps.removeAll { now.timeIntervalSince($0) > windowDuration } if timestamps.count < maxRequests { timestamps.append(now) return true } return false } }}
// Example: no more than 5 requests per 10 secondslet windowLimiter = SlidingWindowLimiter(maxRequests: 5, windowDuration: 10)3. Throttling with DispatchQueue
Uses built-in GCD mechanisms for forced de