Table of contents

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.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon