How do I handle network reachability changes with Alamofire?
Network connectivity is a critical aspect of iOS app development, especially when your application heavily relies on web requests and data synchronization. Alamofire provides a robust solution for monitoring network reachability changes through its NetworkReachabilityManager
class, allowing you to create responsive applications that gracefully handle connectivity issues.
Understanding NetworkReachabilityManager
Alamofire's NetworkReachabilityManager
is built on top of Apple's SystemConfiguration framework and provides a Swift-friendly interface for monitoring network reachability. It can detect changes in network status and notify your application when connectivity becomes available or unavailable.
Basic Network Reachability Setup
Here's how to set up basic network reachability monitoring with Alamofire:
import Alamofire
class NetworkManager {
static let shared = NetworkManager()
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
private init() {
startNetworkReachabilityObserver()
}
func startNetworkReachabilityObserver() {
reachabilityManager?.startListening { status in
switch status {
case .notReachable:
print("Network not reachable")
self.handleNetworkUnavailable()
case .reachable(.ethernetOrWiFi):
print("Network reachable via WiFi/Ethernet")
self.handleNetworkAvailable(isWiFi: true)
case .reachable(.cellular):
print("Network reachable via Cellular")
self.handleNetworkAvailable(isWiFi: false)
case .unknown:
print("Network status unknown")
self.handleNetworkUnknown()
}
}
}
private func handleNetworkAvailable(isWiFi: Bool) {
// Resume queued requests or sync data
NotificationCenter.default.post(name: .networkBecameReachable, object: nil)
}
private func handleNetworkUnavailable() {
// Cache requests or show offline UI
NotificationCenter.default.post(name: .networkBecameUnreachable, object: nil)
}
private func handleNetworkUnknown() {
// Handle unknown network state
}
deinit {
reachabilityManager?.stopListening()
}
}
// Custom notification names
extension Notification.Name {
static let networkBecameReachable = Notification.Name("networkBecameReachable")
static let networkBecameUnreachable = Notification.Name("networkBecameUnreachable")
}
Advanced Reachability Implementation
For more sophisticated network handling, you can create a comprehensive network service that integrates reachability monitoring with request management:
import Alamofire
import Foundation
protocol NetworkReachabilityDelegate: AnyObject {
func networkDidBecomeReachable()
func networkDidBecomeUnreachable()
func networkConnectionTypeDidChange(isWiFi: Bool)
}
class AdvancedNetworkManager {
static let shared = AdvancedNetworkManager()
private let reachabilityManager: NetworkReachabilityManager?
private let session: Session
weak var delegate: NetworkReachabilityDelegate?
// Queue for storing failed requests during network unavailability
private var pendingRequests: [() -> Void] = []
// Current network status
private(set) var isNetworkReachable = true
private(set) var isWiFiConnection = true
private init() {
// Initialize with custom configuration
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
self.session = Session(configuration: configuration)
self.reachabilityManager = NetworkReachabilityManager(host: "www.apple.com")
setupReachabilityMonitoring()
}
private func setupReachabilityMonitoring() {
reachabilityManager?.startListening { [weak self] status in
DispatchQueue.main.async {
self?.handleReachabilityChange(status)
}
}
}
private func handleReachabilityChange(_ status: NetworkReachabilityManager.NetworkReachabilityStatus) {
let wasReachable = isNetworkReachable
let wasWiFi = isWiFiConnection
switch status {
case .notReachable:
isNetworkReachable = false
isWiFiConnection = false
if wasReachable {
delegate?.networkDidBecomeUnreachable()
handleNetworkLost()
}
case .reachable(.ethernetOrWiFi):
isNetworkReachable = true
isWiFiConnection = true
if !wasReachable {
delegate?.networkDidBecomeReachable()
handleNetworkRestored()
} else if !wasWiFi {
delegate?.networkConnectionTypeDidChange(isWiFi: true)
}
case .reachable(.cellular):
isNetworkReachable = true
isWiFiConnection = false
if !wasReachable {
delegate?.networkDidBecomeReachable()
handleNetworkRestored()
} else if wasWiFi {
delegate?.networkConnectionTypeDidChange(isWiFi: false)
}
case .unknown:
// Handle unknown state conservatively
break
}
}
private func handleNetworkLost() {
print("Network connection lost - implementing offline strategy")
// Implement offline data caching or user notification
}
private func handleNetworkRestored() {
print("Network connection restored - processing pending requests")
processPendingRequests()
}
private func processPendingRequests() {
let requests = pendingRequests
pendingRequests.removeAll()
requests.forEach { request in
request()
}
}
// Smart request method that handles network availability
func performRequest<T: Decodable>(
_ url: String,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
responseType: T.Type,
completion: @escaping (Result<T, Error>) -> Void
) {
guard isNetworkReachable else {
// Queue request for later execution
let queuedRequest = {
self.performRequest(url, method: method, parameters: parameters, responseType: responseType, completion: completion)
}
pendingRequests.append(queuedRequest)
completion(.failure(NetworkError.networkUnavailable))
return
}
session.request(url, method: method, parameters: parameters)
.validate()
.responseDecodable(of: responseType) { response in
switch response.result {
case .success(let data):
completion(.success(data))
case .failure(let error):
completion(.failure(error))
}
}
}
deinit {
reachabilityManager?.stopListening()
}
}
// Custom network errors
enum NetworkError: Error, LocalizedError {
case networkUnavailable
case connectionTypeChanged
var errorDescription: String? {
switch self {
case .networkUnavailable:
return "Network connection is not available"
case .connectionTypeChanged:
return "Network connection type has changed"
}
}
}
Integrating Reachability with UI Components
Here's how to integrate network reachability monitoring with your UI components:
import UIKit
import Alamofire
class ViewController: UIViewController {
@IBOutlet weak var networkStatusLabel: UILabel!
@IBOutlet weak var refreshButton: UIButton!
private let networkManager = AdvancedNetworkManager.shared
override func viewDidLoad() {
super.viewDidLoad()
setupNetworkObservers()
networkManager.delegate = self
updateUIForCurrentNetworkStatus()
}
private func setupNetworkObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(networkBecameReachable),
name: .networkBecameReachable,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(networkBecameUnreachable),
name: .networkBecameUnreachable,
object: nil
)
}
@objc private func networkBecameReachable() {
updateUIForNetworkStatus(isReachable: true)
}
@objc private func networkBecameUnreachable() {
updateUIForNetworkStatus(isReachable: false)
}
private func updateUIForCurrentNetworkStatus() {
updateUIForNetworkStatus(isReachable: networkManager.isNetworkReachable)
}
private func updateUIForNetworkStatus(isReachable: Bool) {
DispatchQueue.main.async {
if isReachable {
self.networkStatusLabel.text = "Connected"
self.networkStatusLabel.textColor = .systemGreen
self.refreshButton.isEnabled = true
} else {
self.networkStatusLabel.text = "No Connection"
self.networkStatusLabel.textColor = .systemRed
self.refreshButton.isEnabled = false
self.showOfflineAlert()
}
}
}
private func showOfflineAlert() {
let alert = UIAlertController(
title: "No Internet Connection",
message: "Please check your internet connection and try again.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
@IBAction func refreshButtonTapped(_ sender: UIButton) {
loadData()
}
private func loadData() {
// Example API request with automatic retry on network restore
networkManager.performRequest(
"https://api.example.com/data",
responseType: [String: Any].self
) { result in
DispatchQueue.main.async {
switch result {
case .success(let data):
print("Data loaded successfully: \(data)")
// Update UI with data
case .failure(let error):
print("Failed to load data: \(error)")
// Handle error appropriately
}
}
}
}
}
extension ViewController: NetworkReachabilityDelegate {
func networkDidBecomeReachable() {
print("Network became reachable")
updateUIForNetworkStatus(isReachable: true)
}
func networkDidBecomeUnreachable() {
print("Network became unreachable")
updateUIForNetworkStatus(isReachable: false)
}
func networkConnectionTypeDidChange(isWiFi: Bool) {
print("Connection type changed to: \(isWiFi ? "WiFi" : "Cellular")")
// Adjust data usage strategy based on connection type
if !isWiFi {
// Reduce image quality or limit background sync on cellular
}
}
}
Best Practices for Network Reachability
1. Graceful Degradation
Implement offline capabilities and queue management for network requests:
class OfflineDataManager {
private var offlineQueue: [NetworkRequest] = []
struct NetworkRequest {
let url: String
let method: HTTPMethod
let parameters: Parameters?
let timestamp: Date
}
func queueRequest(url: String, method: HTTPMethod = .get, parameters: Parameters? = nil) {
let request = NetworkRequest(
url: url,
method: method,
parameters: parameters,
timestamp: Date()
)
offlineQueue.append(request)
}
func processQueuedRequests() {
let requests = offlineQueue
offlineQueue.removeAll()
for request in requests {
// Process each queued request
AF.request(request.url, method: request.method, parameters: request.parameters)
.response { response in
// Handle response
}
}
}
}
2. Connection Type Optimization
Optimize your requests based on the connection type to improve user experience on cellular networks:
func optimizeForConnectionType() {
if networkManager.isWiFiConnection {
// Full quality images and aggressive prefetching
enableHighBandwidthFeatures()
} else {
// Reduced image quality and conservative data usage
enableLowBandwidthMode()
}
}
private func enableHighBandwidthFeatures() {
// Enable high-quality image downloads
// Prefetch additional data
}
private func enableLowBandwidthMode() {
// Reduce image resolution
// Limit background sync
// Show data usage warnings
}
Testing Network Reachability
For testing network reachability functionality, you can simulate different network conditions:
# Using Network Link Conditioner (requires Xcode Additional Tools)
# Set network conditions to "100% Loss" to test offline behavior
# Using iOS Simulator
# Device -> Network Link Conditioner -> Enable and set to "100% Loss"
Conclusion
Handling network reachability changes with Alamofire requires a thoughtful approach that combines monitoring, queuing, and user experience considerations. By implementing proper reachability monitoring, you can create robust iOS applications that gracefully handle network connectivity changes while maintaining a smooth user experience.
Remember to test your implementation thoroughly under various network conditions, including switching between WiFi and cellular connections, and complete network outages. This ensures your app remains responsive and user-friendly regardless of connectivity challenges.
For applications that heavily rely on network operations, similar patterns can be applied when monitoring network requests in Puppeteer for web scraping scenarios, or when handling timeouts in Puppeteer to ensure robust data collection even under unstable network conditions.