mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-02 09:31:32 +08:00
406 lines
15 KiB
Objective-C
Executable File
406 lines
15 KiB
Objective-C
Executable File
//
|
|
// RKReachabilityObserver.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 9/14/10.
|
|
// Copyright (c) 2009-2012 RestKit. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#if TARGET_OS_IPHONE
|
|
#import <UIKit/UIKit.h>
|
|
#endif
|
|
|
|
#import "RKReachabilityObserver.h"
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#import "RKLog.h"
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent lcl_cRestKitNetworkReachability
|
|
|
|
@interface RKReachabilityObserver (Private)
|
|
|
|
@property (nonatomic, assign) SCNetworkReachabilityFlags reachabilityFlags;
|
|
|
|
// Internal initializer
|
|
- (id)initWithReachabilityRef:(SCNetworkReachabilityRef)reachabilityRef;
|
|
- (void)scheduleObserver;
|
|
- (void)unscheduleObserver;
|
|
|
|
@end
|
|
|
|
// Constants
|
|
NSString * const RKReachabilityDidChangeNotification = @"RKReachabilityDidChangeNotification";
|
|
NSString * const RKReachabilityFlagsUserInfoKey = @"RKReachabilityFlagsUserInfoKey";
|
|
NSString * const RKReachabilityWasDeterminedNotification = @"RKReachabilityWasDeterminedNotification";
|
|
|
|
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
RKReachabilityObserver *observer = (RKReachabilityObserver *)info;
|
|
observer.reachabilityFlags = flags;
|
|
|
|
[pool release];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
@implementation RKReachabilityObserver
|
|
|
|
@synthesize host = _host;
|
|
@synthesize reachabilityFlags = _reachabilityFlags;
|
|
@synthesize reachabilityDetermined = _reachabilityDetermined;
|
|
@synthesize monitoringLocalWiFi = _monitoringLocalWiFi;
|
|
|
|
+ (RKReachabilityObserver *)reachabilityObserverForAddress:(const struct sockaddr *)address
|
|
{
|
|
return [[[self alloc] initWithAddress:address] autorelease];
|
|
}
|
|
|
|
+ (RKReachabilityObserver *)reachabilityObserverForInternetAddress:(in_addr_t)internetAddress
|
|
{
|
|
struct sockaddr_in address;
|
|
bzero(&address, sizeof(address));
|
|
address.sin_len = sizeof(address);
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.s_addr = htonl(internetAddress);
|
|
return [self reachabilityObserverForAddress:(struct sockaddr *)&address];
|
|
}
|
|
|
|
+ (RKReachabilityObserver *)reachabilityObserverForInternet
|
|
{
|
|
return [self reachabilityObserverForInternetAddress:INADDR_ANY];
|
|
}
|
|
|
|
+ (RKReachabilityObserver *)reachabilityObserverForLocalWifi
|
|
{
|
|
return [self reachabilityObserverForInternetAddress:IN_LINKLOCALNETNUM];
|
|
}
|
|
|
|
+ (RKReachabilityObserver *)reachabilityObserverForHost:(NSString *)hostNameOrIPAddress
|
|
{
|
|
return [[[self alloc] initWithHost:hostNameOrIPAddress] autorelease];
|
|
}
|
|
|
|
- (id)initWithAddress:(const struct sockaddr *)address
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_reachabilityRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, address);
|
|
if (_reachabilityRef == NULL) {
|
|
RKLogWarning(@"Unable to initialize reachability reference");
|
|
[self release];
|
|
self = nil;
|
|
} else {
|
|
// For technical details regarding link-local connections, please
|
|
// see the following source file at Apple's open-source site.
|
|
//
|
|
// http://www.opensource.apple.com/source/bootp/bootp-89/IPConfiguration.bproj/linklocal.c
|
|
//
|
|
_monitoringLocalWiFi = address->sa_len == sizeof(struct sockaddr_in) && address->sa_family == AF_INET && IN_LINKLOCAL(ntohl(((const struct sockaddr_in *)address)->sin_addr.s_addr));
|
|
|
|
// Save the IP address
|
|
char str[INET_ADDRSTRLEN];
|
|
inet_ntop(AF_INET, &((const struct sockaddr_in *)address)->sin_addr, str, INET_ADDRSTRLEN);
|
|
_host = [[NSString alloc] initWithCString:str encoding:NSUTF8StringEncoding];
|
|
|
|
if (_monitoringLocalWiFi) {
|
|
RKLogInfo(@"Reachability observer initialized for Local Wifi");
|
|
} else if (address->sa_len == sizeof(struct sockaddr_in) && address->sa_family == AF_INET) {
|
|
RKLogInfo(@"Reachability observer initialized with IP address: %@.", _host);
|
|
}
|
|
|
|
// We can immediately determine reachability to an IP address
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// Obtain the flags after giving other objects a chance to observe us
|
|
[self getFlags];
|
|
});
|
|
|
|
// Schedule the observer
|
|
[self scheduleObserver];
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithHost:(NSString *)hostNameOrIPAddress
|
|
{
|
|
// Determine if the string contains a hostname or IP address
|
|
struct sockaddr_in sa;
|
|
char *hostNameOrIPAddressCString = (char *)[hostNameOrIPAddress UTF8String];
|
|
int result = inet_pton(AF_INET, hostNameOrIPAddressCString, &(sa.sin_addr));
|
|
if (result != 0) {
|
|
// IP Address
|
|
struct sockaddr_in remote_saddr;
|
|
|
|
bzero(&remote_saddr, sizeof(struct sockaddr_in));
|
|
remote_saddr.sin_len = sizeof(struct sockaddr_in);
|
|
remote_saddr.sin_family = AF_INET;
|
|
inet_aton(hostNameOrIPAddressCString, &(remote_saddr.sin_addr));
|
|
|
|
return [self initWithAddress:(struct sockaddr *)&remote_saddr];
|
|
}
|
|
|
|
// Hostname
|
|
self = [self init];
|
|
if (self) {
|
|
_host = [hostNameOrIPAddress retain];
|
|
_reachabilityRef = SCNetworkReachabilityCreateWithName(CFAllocatorGetDefault(), hostNameOrIPAddressCString);
|
|
RKLogInfo(@"Reachability observer initialized with hostname %@", hostNameOrIPAddress);
|
|
if (_reachabilityRef == NULL) {
|
|
RKLogWarning(@"Unable to initialize reachability reference");
|
|
[self release];
|
|
self = nil;
|
|
} else {
|
|
[self scheduleObserver];
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
RKLogTrace(@"Deallocating reachability observer %@", self);
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
[self unscheduleObserver];
|
|
if (_reachabilityRef) {
|
|
CFRelease(_reachabilityRef);
|
|
}
|
|
[_host release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (BOOL)getFlags
|
|
{
|
|
SCNetworkReachabilityFlags flags = 0;
|
|
BOOL result = SCNetworkReachabilityGetFlags(_reachabilityRef, &flags);
|
|
if (result) self.reachabilityFlags = flags;
|
|
return result;
|
|
}
|
|
|
|
- (NSString *)stringFromNetworkStatus:(RKReachabilityNetworkStatus)status
|
|
{
|
|
switch (status) {
|
|
case RKReachabilityIndeterminate:
|
|
return @"RKReachabilityIndeterminate";
|
|
break;
|
|
|
|
case RKReachabilityNotReachable:
|
|
return @"RKReachabilityNotReachable";
|
|
break;
|
|
|
|
case RKReachabilityReachableViaWiFi:
|
|
return @"RKReachabilityReachableViaWiFi";
|
|
break;
|
|
|
|
case RKReachabilityReachableViaWWAN:
|
|
return @"RKReachabilityReachableViaWWAN";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)reachabilityFlagsDescription
|
|
{
|
|
return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
|
|
#if TARGET_OS_IPHONE
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
|
|
#else
|
|
// If we are not on iOS, always output a dash for WWAN
|
|
'-',
|
|
#endif
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
|
|
}
|
|
|
|
- (RKReachabilityNetworkStatus)networkStatus
|
|
{
|
|
NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL reachabilityRef");
|
|
RKReachabilityNetworkStatus status = RKReachabilityNotReachable;
|
|
|
|
if (!self.reachabilityDetermined) {
|
|
RKLogTrace(@"Reachability observer %@ has not yet established reachability. networkStatus = %@", self, @"RKReachabilityIndeterminate");
|
|
return RKReachabilityIndeterminate;
|
|
}
|
|
|
|
RKLogTrace(@"Reachability Flags: %@\n", [self reachabilityFlagsDescription]);
|
|
|
|
// If we are observing WiFi, we are only reachable via WiFi when flags are direct
|
|
if (self.isMonitoringLocalWiFi) {
|
|
if ((_reachabilityFlags & kSCNetworkReachabilityFlagsReachable) && (_reachabilityFlags & kSCNetworkReachabilityFlagsIsDirect)) {
|
|
// <-- reachable AND direct
|
|
status = RKReachabilityReachableViaWiFi;
|
|
} else {
|
|
// <-- NOT reachable OR NOT direct
|
|
status = RKReachabilityNotReachable;
|
|
}
|
|
} else {
|
|
if ((_reachabilityFlags & kSCNetworkReachabilityFlagsReachable)) {
|
|
// <-- reachable
|
|
#if TARGET_OS_IPHONE
|
|
if ((_reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN)) {
|
|
// <-- reachable AND is wireless wide-area network (iOS only)
|
|
status = RKReachabilityReachableViaWWAN;
|
|
} else {
|
|
#endif
|
|
// <-- reachable AND is NOT wireless wide-area network (iOS only)
|
|
if ((_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionOnTraffic) || (_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionOnDemand)) {
|
|
// <-- reachable, on-traffic OR on-demand connection
|
|
if ((_reachabilityFlags & kSCNetworkReachabilityFlagsInterventionRequired)) {
|
|
// <-- reachable, on-traffic OR on-demand connection, intervention required
|
|
status = (_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired) ? RKReachabilityNotReachable : RKReachabilityReachableViaWiFi;
|
|
} else {
|
|
// <-- reachable, on-traffic OR on-demand connection, intervention NOT required
|
|
status = RKReachabilityReachableViaWiFi;
|
|
}
|
|
} else {
|
|
// <-- reachable, NOT on-traffic OR on-demand connection
|
|
status = (_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired) ? RKReachabilityNotReachable : RKReachabilityReachableViaWiFi;
|
|
}
|
|
#if TARGET_OS_IPHONE
|
|
}
|
|
#endif
|
|
} else {
|
|
// <-- NOT reachable
|
|
status = RKReachabilityNotReachable;
|
|
}
|
|
}
|
|
|
|
RKLogTrace(@"Reachability observer %@ determined networkStatus = %@", self, [self stringFromNetworkStatus:status]);
|
|
return status;
|
|
}
|
|
|
|
#pragma Reachability Flag Introspection
|
|
|
|
- (void)validateIntrospection
|
|
{
|
|
NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
|
|
NSAssert(self.isReachabilityDetermined, @"Cannot inspect reachability state: no reachabilityFlags available. Be sure to check isReachabilityDetermined");
|
|
}
|
|
|
|
- (BOOL)isNetworkReachable
|
|
{
|
|
[self validateIntrospection];
|
|
BOOL reachable = (RKReachabilityNotReachable != [self networkStatus]);
|
|
RKLogDebug(@"Reachability observer %@ determined isNetworkReachable = %d", self, reachable);
|
|
return reachable;
|
|
}
|
|
|
|
- (BOOL)isConnectionRequired
|
|
{
|
|
[self validateIntrospection];
|
|
BOOL required = (_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired);
|
|
|
|
RKLogDebug(@"Reachability observer %@ determined isConnectionRequired = %d", self, required);
|
|
return required;
|
|
}
|
|
|
|
- (BOOL)isReachableViaWWAN
|
|
{
|
|
[self validateIntrospection];
|
|
return self.networkStatus == RKReachabilityReachableViaWWAN;
|
|
}
|
|
|
|
- (BOOL)isReachableViaWiFi
|
|
{
|
|
[self validateIntrospection];
|
|
return self.networkStatus == RKReachabilityReachableViaWiFi;
|
|
}
|
|
|
|
- (BOOL)isConnectionOnDemand
|
|
{
|
|
[self validateIntrospection];
|
|
return ((_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired) &&
|
|
(_reachabilityFlags & (kSCNetworkReachabilityFlagsConnectionOnTraffic |
|
|
kSCNetworkReachabilityFlagsConnectionOnDemand)));
|
|
|
|
}
|
|
|
|
- (BOOL)isInterventionRequired
|
|
{
|
|
[self validateIntrospection];
|
|
return ((_reachabilityFlags & kSCNetworkReachabilityFlagsConnectionRequired) &&
|
|
(_reachabilityFlags & kSCNetworkReachabilityFlagsInterventionRequired));
|
|
}
|
|
|
|
#pragma mark Observer scheduling
|
|
|
|
- (void)scheduleObserver
|
|
{
|
|
SCNetworkReachabilityContext context = { .info = self };
|
|
RKLogDebug(@"Scheduling reachability observer %@ in main dispatch queue", self);
|
|
if (! SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) {
|
|
RKLogWarning(@"%@: SCNetworkReachabilitySetCallback() failed: %s", self, SCErrorString(SCError()));
|
|
return;
|
|
}
|
|
|
|
if (! SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, dispatch_get_main_queue())) {
|
|
RKLogWarning("%@: SCNetworkReachabilitySetDispatchQueue() failed: %s", self, SCErrorString(SCError()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
- (void)unscheduleObserver
|
|
{
|
|
if (_reachabilityRef) {
|
|
RKLogDebug(@"%@: Unscheduling reachability observer from main dispatch queue", self);
|
|
if (! SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, NULL)) {
|
|
RKLogWarning("%@: SCNetworkReachabilitySetDispatchQueue() failed: %s\n", self, SCErrorString(SCError()));
|
|
return;
|
|
}
|
|
} else {
|
|
RKLogDebug(@"%@: Failed to unschedule reachability observer %@: reachability reference is nil.", self, _reachabilityRef);
|
|
}
|
|
}
|
|
|
|
- (void)setReachabilityFlags:(SCNetworkReachabilityFlags)reachabilityFlags
|
|
{
|
|
// Save the reachability flags
|
|
_reachabilityFlags = reachabilityFlags;
|
|
|
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:reachabilityFlags] forKey:RKReachabilityFlagsUserInfoKey];
|
|
|
|
if (! self.reachabilityDetermined) {
|
|
_reachabilityDetermined = YES;
|
|
RKLogInfo(@"Network availability has been determined for reachability observer %@", self);
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKReachabilityWasDeterminedNotification object:self userInfo:userInfo];
|
|
}
|
|
|
|
// Post a notification to notify the client that the network reachability changed.
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKReachabilityDidChangeNotification object:self userInfo:userInfo];
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"<%@: %p host=%@ isReachabilityDetermined=%@ isMonitoringLocalWiFi=%@ reachabilityFlags=%@>",
|
|
NSStringFromClass([self class]), self, self.host, self.isReachabilityDetermined ? @"YES" : @"NO",
|
|
self.isMonitoringLocalWiFi ? @"YES" : @"NO", [self reachabilityFlagsDescription]];
|
|
}
|
|
|
|
@end
|