AFNetworking 3.0 源码笔记 AFNetworkReachabilityManager

AFNetworkReachabilityManager 是AFNetworking框架里面 相对低耦合的一个部分,所以拿AFNetworkReachabilityManager开刀是个不错的算择。

首先来看看AFNetworkReachability的功能:
'AFNetworkReachabilityManager' 监视域名和WWAN和WiFi接口地址的Reachability。

然而网络一般有两种(蜂窝WWAN、WIFI)加上无网络和未知错误,一共四种网络状态
于是有了下面的枚举类型:

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};

通过这个需求我们引出了下面四个属性:

/**
 当前网络可用性的状态
 */
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
 网络当前是否可用
 */
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;

/**
 WWAN网络当前是否可用
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
 WiFI网络当前是否可用
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;

*注:BOOL类型要给出getter方法以添加 is 或者 have 前缀。

接下来是初始化方法,对于这种全局监控的类来说,单例肯定是必要的。

/**
 Returns the shared network reachability manager.
 */
+ (instancetype)sharedManager;

因为我们还要对特定的域名(domain)和网络地址做监控所以有了以下的初始化方法:

/**
 Creates and returns a network reachability manager for the specified domain.

 @param domain The domain used to evaluate network reachability.

 @return An initialized network reachability manager, actively monitoring the specified domain.
 */
+ (instancetype)managerForDomain:(NSString *)domain;

/**
 Creates and returns a network reachability manager for the socket address.

 @param address The socket address (`sockaddr_in6`) used to evaluate network reachability.

 @return An initialized network reachability manager, actively monitoring the specified socket address.
 */
+ (instancetype)managerForAddress:(const void *)address;

首先需要禁止用户调用默认的构造方法init:

/**
 *  Initializes an instance of a network reachability manager
 *
 *  @return nil as this method is unavailable
 */
- (nullable instancetype)init NS_UNAVAILABLE;

然而如果有多种初始化方法就需要一个总的初始化方法,基本所有的初始化方法都应该调用该总初始化方法。于是有了下面的初始化方法:

/**
 Initializes an instance of a network reachability manager from the specified reachability object.

 @param reachability The reachability object to monitor.

 @return An initialized network reachability manager, actively monitoring the specified reachability.
 */
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;

*注:NS_DESIGNATED_INITIALIZER表明该初始化方法是总初始化方法,所有的初始化方法(包括子类的)都应该代用该方法。

就如同C++,应该有一个默认构造于是有了下面的默认构造方法:

/**
 Creates and returns a network reachability manager with the default socket address.
 
 @return An initialized network reachability manager, actively monitoring the default socket address.
 */
+ (instancetype)manager;

网络变化了,需要通过回调告诉用户,一般传值有如下方法:
1.代理 2.通知 3.block 4.KVO
KVO会添加用户操作所以pass,通知用block替代以减少用户操作。block是用于一对一的,当你设置了新的block的时候上一个block会失效,然而通知则不同他是一对多的。
关于block我们有了以下方法:

/**
 Sets a callback to be executed when the network availability of the `baseURL` host changes.

 @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`.
 */
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;

关于通知我们定义一下两个NSString常量:一个用来表示通知的string,一个用来获取通知userinfo中的信息

/**
 Posted when network reachability changes.
 This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability.

 @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (`Prefix.pch`).
 */
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;

因为一般情况下是单例,无法销毁,关闭监听的公开方法是必要的,既然有关闭监听的方法,开启监听的方法也是必要的了。所以有了如下两个方法:

/**
 Starts monitoring for changes in network reachability status.
 */
- (void)startMonitoring;

/**
 Stops monitoring for changes in network reachability status.
 */
- (void)stopMonitoring;

作为一个国际化的开发空间,反映出当前网络状态的本地化文字也是必要的。所以有了如下方法:

/**
 Returns a localized string representation of the current network reachability status.
 */
- (NSString *)localizedNetworkReachabilityStatusString;

当然也有个C方法是转换当前的AFNetworkReachabilityStatus到字符穿的

FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);

之所以用C方法并且用了FOUNDATION_EXPORT关键字,我猜测是为了进一步简化用户操作。而且字符串检测速度很快直接用"=="就可以了。

=================================== 我是分割线 ===================================

来看看如何实现这些方法:
首先这个类是通过苹果的SCNetworkReachabilityRef来实现功能的,所以要有一个SCNetworkReachabilityRef的实例变量。这里我们定一个SCNetworkReachabilityRef类型的属性:

@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;

因为.h中networkReachabilityStatus是readonly,所以这里要把他变成readwrite

@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

创建一个block,来存储用户的block

typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;

接下来实现总初始化方法:
想法很简答,把参数传过来的SCNetworkReachabilityRef给保存到实例变量中,并且初始化networkReachabilityStatus的数值。

- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
    self = [super init];
    if (!self) {
        return nil;
    }

    _networkReachability = CFRetain(reachability);
    self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;

    return self;
}

*注:SCNetworkReachabilityRef是C层的结构体指针,需要用CFRetain来持有。并且在不用的时候CFRelease释放。

接下来实现另外两个需要参数的工厂方法。这里所有的构造方法最终都要调用总构造,所以我们要把 domain 和 address 变成 SCNetworkReachabilityRef,然后传递给总构造方法。
SCNetworkReachabilityRef 有三个构造方法,这里用的是其中的两个WithName 和 WithAddress。

+ (instancetype)managerForDomain:(NSString *)domain {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);

    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
    
    CFRelease(reachability);

    return manager;
}
+ (instancetype)managerForAddress:(const void *)address {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    CFRelease(reachability);
    
    return manager;
}

*注:这里要CFRelease(reachability),因为在总构造方法中已经CFRetain(reachability)了。

接下来实现的是默认的工厂方法,看过《unix网络编程》的人对这些代码会比较熟悉。
首先需要一个 sockaddr_in 的结构体 来存储 socket 地址。
然后再用 bzero 把地址清空。
配置socket地址的基本属性sin_family 和 sin_len。
当然这里还需要根据系统来区分创建的是ipv6还是ipv4。
代码如下:

+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
    struct sockaddr_in6 address;
    bzero(&address, sizeof(address));
    address.sin6_len = sizeof(address);
    address.sin6_family = AF_INET6;
#else
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
#endif
    return [self managerForAddress:&address];
}

- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
    self = [super init];
    if (!self) {
        return nil;
    }

    _networkReachability = CFRetain(reachability);
    self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;

    return self;
}

最后来实现我们的单例方法,很简单的GCD调用:

+ (instancetype)sharedManager {
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [self manager];
    });

    return _sharedManager;
}

当然因为需要禁止用户调用默认的init方法所以做如下处理:

- (instancetype)init NS_UNAVAILABLE
{
    return nil;
}

先别急着写别的,先把dealloc写了,以免忘记(如果忘了可能会造成大麻烦啊)。
dealloc方法需要做两件事:
1.将实例变量该release的release掉,这里指的就是networkReachabilityStatus。当然在release之前需要判断是否为空,如果已经释放完了,再释放会崩溃的。
2.停止监听,调用自己的stopMonitoring方法就好。

- (void)dealloc {
    [self stopMonitoring];
    
    if (_networkReachability != NULL) {
        CFRelease(_networkReachability);
    }
}

然后实现咱们之前设置的三个表示网络状态的getter

- (BOOL)isReachable {
    return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}

- (BOOL)isReachableViaWWAN {
    return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}

- (BOOL)isReachableViaWiFi {
    return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}

=================================== 我是分割线 ===================================
接下来就是重头戏了startMonitoring。
这里挺复杂的我们先看源码,一步一步来:

- (void)startMonitoring {
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
    };

    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}
[self stopMonitoring];

在任何需要开始之前,先调用停止的方法。以免重复弃用(socket连接,动画方法等 都是如此)。

if (!self.networkReachability) {
        return;
    }

容错机制

Boolean
SCNetworkReachabilitySetCallback        (
                        SCNetworkReachabilityRef            target,
                        SCNetworkReachabilityCallBack   __nullable  callout,
                        SCNetworkReachabilityContext    * __nullable    context
                        )               __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);

这个是启动SCNetworkReachabilityRef
参数一 要启动的SCNetworkReachabilityRef
参数二 启动的 SCNetworkReachabilityRef变化的时候回调的SCNetworkReachabilityCallBack类型的函数指针
参数三 和参数二相相关的上下文

这里要讲一下参数二
SCNetworkReachabilityCallBack 定义如下

typedef void (*SCNetworkReachabilityCallBack)   (
                        SCNetworkReachabilityRef            target,
                        SCNetworkReachabilityFlags          flags,
                        void                 *  __nullable  info
                        );

函数指针实际就是OC的block,大家完全可以把这个当做一个block看待。我们像是定义一个block一样来定义一个函数指针。

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {

}

意思就是说当
SCNetworkReachabilitySetCallback(::) 第一个参数启动的SCNetworkReachabilityRef发生变化的时候回直接调用这个函数。
这个函数中第一个参数(SCNetworkReachabilityRef __unused target)就是咱们启动的SCNetworkReachabilityRef。
第二个参数就是启动的SCNetworkReachabilityRef所变成的网络状态,具体数值如下:

typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
    kSCNetworkReachabilityFlagsTransientConnection  = 1<<0,
    kSCNetworkReachabilityFlagsReachable        = 1<<1,
    kSCNetworkReachabilityFlagsConnectionRequired   = 1<<2,
    kSCNetworkReachabilityFlagsConnectionOnTraffic  = 1<<3,
    kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
    kSCNetworkReachabilityFlagsConnectionOnDemand   = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0)
    kSCNetworkReachabilityFlagsIsLocalAddress   = 1<<16,
    kSCNetworkReachabilityFlagsIsDirect     = 1<<17,
#if TARGET_OS_IPHONE
    kSCNetworkReachabilityFlagsIsWWAN       = 1<<18,
#endif  // TARGET_OS_IPHONE

    kSCNetworkReachabilityFlagsConnectionAutomatic  = kSCNetworkReachabilityFlagsConnectionOnTraffic
};

第三个参数就是咱们在SCNetworkReachabilitySetCallback中传的第三个参数context中的一个info成员。

到这里

SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);

这行算是解释完了。

但是我们还需要一个context,所以上面的

SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};

就是来创建这个的。

这个结构体原型如下:

typedef struct {
    CFIndex     version;
    void *      __nullable info;
    const void  * __nonnull (* __nullable retain)(const void *info);
    void        (* __nullable release)(const void *info);
    CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;

第一个是版本号。
第二个是在SCNetworkReachabilitySetCallback(::)传给第二个函数指针的第二个参数info成员。
第三个 对于info的retain操作的函数指针
第四个 对于info的release操作的函数指针
第五个 描述

这里第一个填0就好,最后一个填NULL就好,中间还差三个。

这里我们要创建一个info,这个info是要最后传递给我们之前创建的C函数

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info)

中的。
而这个函数我们的目的是把第二个参数(网络状态),发送一个通知 并且 调用我们之前在.h中放出的

- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;

block回调。
但是问题来了! 这个是C函数,没有self.的操作!我们要调用之前的block不能用self.networkReachabilityStatusBlock来获取这个block,这时候info就排上用场了!我们要把这个self.networkReachabilityStatusBlock来放到info的位置,把这个self.networkReachabilityStatusBlock传过来就好了。

创建info

__weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
    };

回到4.
第二个参数通过第6步我们就得到了(AFNetworkReachabilityStatusBlock callback)

后面两个参数,既然知道了info是block,所以也可以的出来了。

static const void * AFNetworkReachabilityRetainCallback(const void *info) {
    return Block_copy(info);
}
static void AFNetworkReachabilityReleaseCallback(const void *info) {
    if (info) {
        Block_release(info);
    }
}

再回到第4部,把第7部的两个函数指针给到第3个和第4个参数里面,至此我们的context终于完成了。

然后回到第3步我们创建的C函数

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {

}

这里第3个参数就是一个block了。

我们再创建一个C函数用来发送通知和回调block的。

/**
 * Queue a status change notification for the main thread.
 *
 * This is done to ensure that the notifications are received in the same order
 * as they are sent. If notifications are sent directly, it is possible that
 * a queued notification (for an earlier status condition) is processed after
 * the later update, resulting in the listener being left in the wrong state.
 */
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block(status);
        }
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
    });
}

然后调用这个函数

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

这里

 static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags)

是把系统的flag转换成我们自己的AFNetworkReachabilityStatus
实现如下:

static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
    BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
    BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
    BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
    BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
    BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));

    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
    if (isNetworkReachable == NO) {
        status = AFNetworkReachabilityStatusNotReachable;
    }
#if TARGET_OS_IPHONE
    else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
        status = AFNetworkReachabilityStatusReachableViaWWAN;
    }
#endif
    else {
        status = AFNetworkReachabilityStatusReachableViaWiFi;
    }

    return status;
}

然后这里把这个放到Runloop中启动

SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

最后我们再回到开始,因为第一次用户开始startMonitoring的时候,self.networkReachability的变化是不会计算在内的,所以不会产生通知 和 回调block,所以这里我们要手动调用一次。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });

=================================== 我是分割线 ===================================

接下来就是收尾工作了:
stopMonitoring

- (void)stopMonitoring {
    if (!self.networkReachability) {
        return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

本地化语言

NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
    switch (status) {
        case AFNetworkReachabilityStatusNotReachable:
            return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWWAN:
            return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWiFi:
            return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusUnknown:
        default:
            return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
    }
}

KVO

#pragma mark - NSKeyValueObserving

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
        return [NSSet setWithObject:@"networkReachabilityStatus"];
    }

    return [super keyPathsForValuesAffectingValueForKey:key];
}

如果networkReachabilityStatus发生变化则reachable 、reachableViaWWAN 、reachableViaWiFi调用自己的getter方法。

完结散花。

写在最后,如果大家喜欢帮忙点下“关注”和“喜欢”,大家的鼓励是我最大的动力。

你可能感兴趣的:(AFNetworking 3.0 源码笔记 AFNetworkReachabilityManager)