Fix: SCNetworkReachability Errors in Swift

Fix: SCNetworkReachability Errors in Swift

Written by

in

Quick answer: Legacy SCNetworkReachability code fails in modern Swift because C pointer APIs, sizeofValue, and SCNetworkReachabilityFlags types changed. Replace SystemConfiguration with Apple’s modern NWPathMonitor from the Network framework, or update legacy reachability structs with MemoryLayout and explicit OptionSet initialization.

Why this happens

Compiling legacy SCNetworkReachability snippets in modern versions of Swift triggers errors such as 'Int' is not convertible to 'SCNetworkReachabilityFlags' or Could not find an overload for 'init' that accepts the supplied arguments. These errors occur because early Swift examples bridged C types directly without strict type safety, relying on deprecated helpers like sizeofValue and implicit numeric type conversions that Swift has since removed.

In modern Swift, SCNetworkReachabilityFlags conforms to OptionSet. It cannot be assigned an integer literal directly without wrapping it in SCNetworkReachabilityFlags(rawValue:). Furthermore, C socket structures such as sockaddr_in must be initialized with explicit memberwise values, and pointer conversions require typed pointer rebinding via withMemoryRebound(to:capacity:) rather than direct cast operations.

Beyond compiler errors, Apple has superseded the SystemConfiguration reachability API on iOS with Network.framework. SCNetworkReachability does not distinguish properly between interfaces in multi-path networking scenarios and often reports false positives when a Wi-Fi connection lacks external gateway access.

How to fix it

1. Replace SystemConfiguration with NWPathMonitor (Recommended)

The standard, supported way to check network connectivity across Apple platforms is NWPathMonitor from the Network framework. It does not require bridging unsafe C pointers and provides continuous, asynchronous updates.

Create a dedicated network monitor class:

import Foundation
import Network

final class NetworkMonitor {
    static let shared = NetworkMonitor()

    private let monitor = NWPathMonitor()
    private let queue = DispatchQueue(label: "com.app.networkmonitor")

    private(set) var isConnected: Bool = false
    private(set) var isCellular: Bool = false
    private(set) var isExpensive: Bool = false

    private init() {
        monitor.pathUpdateHandler = { [weak self] path in
            self?.isConnected = path.status == .satisfied
            self?.isCellular = path.usesInterfaceType(.cellular)
            self?.isExpensive = path.isExpensive
        }
        monitor.start(queue: queue)
    }

    func stop() {
        monitor.cancel()
    }
}

You can query the shared monitor synchronously anywhere in your application:

if NetworkMonitor.shared.isConnected {
    print("Internet is reachable")
} else {
    print("No network route available")
}

2. Monitor Network State in SwiftUI

To observe network transitions directly in SwiftUI views, bind NWPathMonitor to an observable model. For iOS 17 and later, use the @Observable macro:

import SwiftUI
import Network

@Observable
final class NetworkState {
    var isConnected: Bool = true
    var isExpensive: Bool = false

    @ObservationIgnored
    private let monitor = NWPathMonitor()
    @ObservationIgnored
    private let queue = DispatchQueue(label: "NetworkStateQueue")

    init() {
        monitor.pathUpdateHandler = { [weak self] path in
            DispatchQueue.main.async {
                self?.isConnected = (path.status == .satisfied)
                self?.isExpensive = path.isExpensive
            }
        }
        monitor.start(queue: queue)
    }

    deinit {
        monitor.cancel()
    }
}

struct ConnectivityBanner: View {
    @State private var network = NetworkState()

    var body: some View {
        if !network.isConnected {
            Text("No Internet Connection")
                .font(.caption)
                .bold()
                .foregroundStyle(.white)
                .padding(.vertical, 6)
                .frame(maxWidth: .infinity)
                .background(Color.red)
        }
    }
}

3. Update Legacy SCNetworkReachability Code to Modern Swift

If you must maintain existing codebase dependencies on SystemConfiguration without introducing new frameworks, rewrite the reachability wrapper to conform to modern Swift pointer safety rules.

Use MemoryLayout instead of sizeofValue, cast pointers with withMemoryRebound, and initialize SCNetworkReachabilityFlags with raw values:

import Foundation
import SystemConfiguration

public final class LegacyReachability {

    public class func isConnectedToNetwork() -> Bool {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)

        let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { pointer in
            pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockAddress in
                SCNetworkReachabilityCreateWithAddress(nil, sockAddress)
            }
        }

        guard let reachability = defaultRouteReachability else {
            return false
        }

        var flags: SCNetworkReachabilityFlags = []
        if !SCNetworkReachabilityGetFlags(reachability, &flags) {
            return false
        }

        let isReachable = flags.contains(.reachable)
        let needsConnection = flags.contains(.connectionRequired)
        let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
        let canConnectWithoutIntervention = canConnectAutomatically && !flags.contains(.interventionRequired)

        return isReachable && (!needsConnection || canConnectWithoutIntervention)
    }
}

4. Remove “Pre-Flight” Connection Checks

Checking network connectivity right before firing a network request is officially documented by Apple as an anti-pattern. Reachability states can change between the moment you check the flag and the moment packets leave the device (the “time-of-check to time-of-use” race condition). A device may also show reachable interfaces while an HTTP endpoint is completely down.

Instead of blocking user operations with a synchronous reachability check, perform the request using URLSession and handle standard connectivity errors:

func fetchData(from url: URL) async {
    do {
        let (data, response) = try await URLSession.shared.data(from: url)
        // Process response...
    } catch let error as URLError {
        switch error.code {
        case .notConnectedToInternet, .networkConnectionLost:
            print("Device is offline or lost connection.")
        case .timedOut:
            print("Server timed out.")
        default:
            print("Network error: \(error.localizedDescription)")
        }
    } catch {
        print("General error: \(error.localizedDescription)")
    }
}

Reserve NWPathMonitor solely for updating UI states (like offline banners) and pausing opportunistic background jobs.

How to prevent it

  • Do not import SystemConfiguration in newly developed modules; import Network instead.
  • Avoid using C pointer manipulation (withUnsafePointer, sockaddr) to detect IP routing in application code.
  • Avoid pre-flight reachability checks before executing URLSession requests.
  • Use NWPath.isExpensive to detect constrained cellular connections instead of inspecting low-level WWAN socket flags.
  • Run network monitor callbacks on dedicated background DispatchQueue instances, dispatching only final UI updates to DispatchQueue.main.

FAQ

Why does SCNetworkReachability return true when there is no internet?

SCNetworkReachability only checks whether the device has an active network interface that knows how to route IP traffic (such as being connected to a local Wi-Fi router). It does not validate whether that local router has upstream connectivity to the global internet.

Can NWPathMonitor run on the main thread?

NWPathMonitor.start(queue:) requires a background DispatchQueue. Supplying DispatchQueue.main can block UI rendering when network interfaces reconfigure; always start the monitor on a serial utility or custom queue and forward state updates to the main thread.

Does NWPathMonitor work in the iOS Simulator?

Yes, NWPathMonitor functions in the iOS Simulator. However, it mirrors the network path of the host macOS machine, meaning cellular simulation flags will not accurately reflect real mobile device routing unless configured via Network Link Conditioner.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.