iOS:如何在不冻结App的前提下轻松检测网络连接(无需Reachability)
Hey! I see you've tried three different network reachability methods on iOS, each with their own frustrations. Let's walk through your current approaches first, then I'll show you a cleaner, more reliable solution that fixes all the pain points you mentioned.
1. Reachability Method
This was a go-to early iOS approach, with your implementation:
- (BOOL)isInternetOk { Reachability *curReach = [Reachability reachabilityWithHostName:@"apple.com"]; NetworkStatus netStatus = [curReach currentReachabilityStatus]; if (netStatus != NotReachable) //if internet connexion ok { return YES; } else { return NO; } }
Limitations:
- Only checks if the device is connected to a network (Wi-Fi/cellular), not if that network can actually access the internet. For example, it'll incorrectly report "connected" if you're on a Wi-Fi with no external access.
- Can't verify server response status codes, so you can't confirm if the target service is actually usable.
2. sendSynchronousRequest (Synchronous Request)
This uses a blocking network request to check connectivity:
- (BOOL)isInternetOk2 { NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init]; NSURL* URL = [NSURL URLWithString:@"https://www.google.com"]; NSError *error = nil; [request setURL:URL]; [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [request setTimeoutInterval:15]; NSData* response2 = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; if (error) { return NO; } else { return YES; } }
Limitations:
- Blocks the main thread entirely. If the request times out, your app will freeze, ruining user experience.
- Moving it to an async thread breaks the ability to get a direct return value (the method will exit before the request finishes).
3. sendAsynchronousRequest (Asynchronous Request)
This non-blocking approach fixes the freeze issue:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.google.com"]]; request.timeoutInterval = 10; [NSURLConnection sendAsynchronousRequest:request queue:myQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; NSLog(@"response status code: %ld, error status : %@", (long)[httpResponse statusCode], error.description); if ((long)[httpResponse statusCode] >= 200 && (long)[httpResponse statusCode]< 400) { // do stuff NSLog(@"Connected!"); } else { NSLog(@"Not connected!"); } }];
Limitations:
- While functional, copying this block of code everywhere leads to massive redundancy, making your codebase hard to maintain.
To fix all your pain points, we'll wrap the network check logic into a singleton utility class. This eliminates code duplication, uses modern NSURLSession (replacing deprecated NSURLConnection), runs asynchronously (no main thread freezes), and accurately verifies server responses.
Step 1: Create the NetworkDetector Utility Class
Header File (NetworkDetector.h)
#import <Foundation/Foundation.h> // Callback closure to return detection results typedef void(^NetworkDetectionCompletion)(BOOL isReachable, NSInteger statusCode, NSError *_Nullable error); @interface NetworkDetector : NSObject // Singleton to ensure one global instance + (instancetype)sharedInstance; // Main method to check network reachability - (void)checkNetworkReachabilityWithCompletion:(NetworkDetectionCompletion)completion; @end
Implementation File (NetworkDetector.m)
#import "NetworkDetector.h" @implementation NetworkDetector + (instancetype)sharedInstance { static NetworkDetector *_sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedInstance = [[self alloc] init]; }); return _sharedInstance; } - (void)checkNetworkReachabilityWithCompletion:(NetworkDetectionCompletion)completion { // Use a stable domain (e.g., Apple's official site, or your own backend) NSURL *checkURL = [NSURL URLWithString:@"https://www.apple.com"]; // Build request with no cache and a reasonable timeout NSURLRequest *request = [NSURLRequest requestWithURL:checkURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0]; // Launch async data task with NSURLSession NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { BOOL isReachable = NO; NSInteger statusCode = 0; if (error) { // Network error (timeout, no connection, etc.) isReachable = NO; } else { // Cast to HTTP response to check status code NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; statusCode = httpResponse.statusCode; // 200-399 = valid server response if (statusCode >= 200 && statusCode < 400) { isReachable = YES; } else { isReachable = NO; } } // Jump back to main thread for UI updates dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { completion(isReachable, statusCode, error); } }); }]; [task resume]; } @end
Step 2: Use the Utility Class Anywhere in Your App
No more copying code! Just call this one line wherever you need to check connectivity:
// Trigger network check [[NetworkDetector sharedInstance] checkNetworkReachabilityWithCompletion:^(BOOL isReachable, NSInteger statusCode, NSError *error) { if (isReachable) { NSLog(@"Network is active, server status code: %ld", (long)statusCode); // Proceed with your business logic (e.g., load data) } else { NSString *errorMessage = error ? error.localizedDescription : @"Server response error"; NSLog(@"Network issue: %@, status code: %ld", errorMessage, (long)statusCode); // Show user an alert or update UI to reflect the issue } }];
Bonus: iOS 13+ Reactive Support with Combine
If your app targets iOS 13 or later, you can use Combine for a cleaner, reactive approach:
Update NetworkDetector.h
#import <Combine/Combine.h> // Add Combine publisher method - (AnyPublisher<BOOL, NSError>)networkReachabilityPublisher;
Update NetworkDetector.m
- (AnyPublisher<BOOL, NSError>)networkReachabilityPublisher { NSURL *checkURL = [NSURL URLWithString:@"https://www.apple.com"]; NSURLRequest *request = [NSURLRequest requestWithURL:checkURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0]; return [[NSURLSession.sharedSession.dataTaskPublisher(for: request) // Map response to a boolean result tryMap:^BOOL(URLSessionDataTaskPublisher.Output output) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)output.response; if (httpResponse.statusCode >= 200 && httpResponse.statusCode < 400) { return YES; } else { NSError *error = [NSError errorWithDomain:@"NetworkDetectorError" code:httpResponse.statusCode userInfo:@{NSLocalizedDescriptionKey:@"Server response invalid"}]; throw error; } }] // Receive results on main thread receive(on: DispatchQueue.main) // Erase to generic publisher type eraseToAnyPublisher(); }
Use the Combine Version
// Hold a reference to avoid premature cancellation @property (nonatomic, strong) AnyCancellable *networkCancellable; // Start detection self.networkCancellable = [[NetworkDetector sharedInstance] networkReachabilityPublisher] .sink(receiveCompletion:^(Subscribers.Completion<NSError *> completion) { if (completion == .failure) { NSLog(@"Network check failed"); } }, receiveValue:^(BOOL isReachable) { if (isReachable) { NSLog(@"Network is online"); // Update UI or run logic } else { NSLog(@"Network is offline"); } });
- No Main Thread Freeze: Fully asynchronous execution keeps your app responsive.
- Accurate Detection: Checks both network connectivity and server response status to avoid false positives.
- No Code Redundancy: Singleton utility class means one source of truth for all network checks.
- Modern API: Uses
NSURLSession(Apple's recommended replacement forNSURLConnection) for better stability. - Flexible: Easily adjust the check domain, timeout, or add custom logic as your app needs change.
内容的提问来源于stack exchange,提问作者ΩlostA

