Implementing iOS Reachability: Detecting Network Changes with SCNetworkReachability
Learn how to use iOS SCNetworkReachability to monitor Wi‑Fi, cellular, and offline states. The guide covers prerequisites, a Swift wrapper, flag interpretation, UI updates, and best‑practice limitations.
20 Sept 2026, 01:07 UTC

Problem & Takeaway
Apps that rely on network data must react quickly when connectivity changes. The system‑provided SCNetworkReachability API gives a lightweight, backward‑compatible way to monitor Wi‑Fi, cellular, and generic network availability on iOS. By registering a callback and interpreting the returned SCNetworkReachabilityFlags, you can update UI, trigger retry logic, and avoid unnecessary network calls when the device is offline.
Desired Outcome
After following this guide you will:
- Set up a reachability monitor that reports connectivity status changes.
- Interpret flags to determine if the network is reachable, if a connection is required, and whether the connection is via Wi‑Fi or cellular.
- Safely update the UI from the callback and clean up the monitor when the object is deinitialized.
- Understand limitations and fallback options such as
NWPathMonitorfor newer iOS versions.
Prerequisites
- iOS 10 or later (SCNetworkReachability is supported through iOS 16). For iOS 12+,
NWPathMonitoris an alternative. - Swift 5.5 or later. The example uses Swift 5.5 syntax.
- Xcode 15 or later for the latest compiler and simulator support.
- Permission to import
SystemConfigurationframework.
Step‑by‑Step Setup
1. Add SystemConfiguration
In your Xcode project, open General → Frameworks, Libraries, and Embedded Content and add SystemConfiguration.framework. Then import it in the file where you’ll monitor reachability:
import SystemConfiguration
2. Create a Reachability Class
Encapsulate the logic in a reusable class so that it can be instantiated in view controllers or app delegates.
final class Reachability {
private var reachabilityRef: SCNetworkReachability?
private let callbackQueue = DispatchQueue(label: "com.example.reachability")
var isReachable: Bool = false
var isUsingWiFi: Bool = false
var isUsingCellular: Bool = false
init?(host: String = "apple.com") {
var zeroAddress = sockaddr_in(
sin_len: UInt8(MemoryLayout<sockaddr_in>.size),
sin_family: sa_family_t(AF_INET),
sin_port: 0,
sin_addr: in_addr(s_addr: inet_addr(host)),
sin_zero: (0,0,0,0,0,0,0,0)
)
guard let reachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
} else { return nil }
self.reachabilityRef = reachability
startMonitoring()
}
deinit { stopMonitoring() }
private func startMonitoring() {
guard let ref = reachabilityRef else { return }
var context = SCNetworkReachabilityContext(
version: 0,
info: UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque()),
retain: nil,
release: nil,
copyDescription: nil
)
SCNetworkReachabilitySetCallback(ref, reachabilityCallback, &context)
SCNetworkReachabilityScheduleWithRunLoop(ref, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
}
private func stopMonitoring() {
guard let ref = reachabilityRef else { return }
SCNetworkReachabilityUnscheduleFromRunLoop(ref, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
}
}
Explanation of key parts:
SCNetworkReachabilityCreateWithAddresscreates a reference that watches the reachability of the given host. Usingapple.comis common for a generic internet check.- The callback receives
SCNetworkReachabilityFlags. We’ll interpret those in a separate function. - Callbacks are scheduled on the main run‑loop because UI updates must happen on the main thread.
3. Define the Callback
Place this function outside the class (C‑style function required by the API). It must be @convention(c) and accept a SCNetworkReachabilityFlags parameter.
@convention(c) private func reachabilityCallback(target: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
guard let info = info else { return }
let instance = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
instance.updateFlags(flags)
}
4. Interpret Flags
Inside the class add a method to map the flags to human‑readable properties. Dispatch UI work to the main queue if needed.
private func updateFlags(_ flags: SCNetworkReachabilityFlags) {
let reachable = flags.contains(.reachable)
let requiresConnection = flags.contains(.connectionRequired)
let usingWWAN = flags.contains(.isWWAN)
// Update state
isReachable = reachable && !requiresConnection
isUsingWiFi = reachable && !requiresConnection && !usingWWAN
isUsingCellular = reachable && !requiresConnection && usingWWAN
// Notify observers on main thread
DispatchQueue.main.async {
NotificationCenter.default.post(name: .reachabilityChanged, object: self)
}
}
Flag meanings (subset used):
.reachable– a network connection is possible..connectionRequired– a connection must be established before data can be sent..isWWAN– the connection is via cellular.
5. Use the Monitor in a View Controller
Instantiate Reachability and observe its notifications to update the UI.
class ViewController: UIViewController {
private var reachability: Reachability?
override func viewDidLoad() {
super.viewDidLoad()
reachability = Reachability()
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged), name: .reachabilityChanged, object: nil)
}
@objc private func reachabilityChanged() {
guard let reach = reachability else { return }
if reach.isReachable {
statusLabel.text = reach.isUsingWiFi ? "Wi‑Fi" : "Cellular"
} else {
statusLabel.text = "Offline"
}
}
deinit { NotificationCenter.default.removeObserver(self) }
}
Expected Checks & Verification
- Compile the project. No build errors should occur if
SystemConfigurationis linked correctly. - Run on a physical device. Toggle Wi‑Fi off, then on again. The
statusLabelshould update accordingly. - Enable Xcode’s Network Link Conditioner and set it to 3G or Offline. The callback should fire and the UI should reflect the changed state.
- Inspect the console logs (add
print("Flags: \(flags)")insideupdateFlags) to see flag bit patterns when connectivity changes.
Recovery & Limitations
- Thread safety: The callback is invoked on the run‑loop you scheduled. Because we schedule on the main run‑loop, UI updates are safe. If you schedule on a background queue, dispatch UI work to
DispatchQueue.main. - VPN & Hotspot:
SCNetworkReachabilitydoes not report VPN status or Wi‑Fi hotspot changes. For a full path analysis useNWPathMonitor(iOS 12+). - Background execution: When the app is backgrounded, reachability callbacks may stop. If you need to maintain state, consider using
background fetchor silent push notifications to re‑check connectivity. - Heavy work in callback: Never perform network requests directly in the callback. Instead, dispatch to a background queue or use
URLSessioncompletion handlers.
Rollback
Because the monitor only registers callbacks and schedules them on the run‑loop, there is no destructive state change. Removing the monitor in deinit is sufficient. If you ever need to cancel monitoring prematurely, call stopMonitoring() before deinitializing.
Conclusion
SCNetworkReachability offers a lightweight, backward‑compatible way to detect network changes on iOS. By wrapping it in a Swift class, interpreting flags, and dispatching UI updates on the main queue, you can give users clear feedback and avoid unnecessary network traffic. For newer projects, consider NWPathMonitor to gain additional path information, but keep SCNetworkReachability in your toolbox for legacy codebases.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.