iOS源码解析—AFNetworking(AFNetworkReachabilityManager)

概述

在AFN框架中,AFURLSessionManager对象的初始化方法中创建了AFNetworkReachabilityManager对象用于监听设备当前连接网络的状态。文本分析一下AFNetworkReachabilityManager。

初始化方法

AFNetworkReachabilityManager提供了4种创建方法,如下:

+ (instancetype)sharedManager; //创建单例对象
+ (instancetype)manager; //创建实例对象
+ (instancetype)managerForDomain:(NSString *)domain; //根据地址名创建实例对象
+ (instancetype)managerForAddress:(const void *)address; //根据sockaddr创建实例对象

第一个方法创建单例对象,代码注释如下:

+ (instancetype)sharedManager {
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [self manager]; //调用manager方法创建对象
    });
    return _sharedManager;
}

sharedManager调用manager方法创建一个AFNetworkReachabilityManager对象,代码注释如下:

+ (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; //创建一个ipv6类型的地址结构体
    bzero(&address, sizeof(address));
    address.sin6_len = sizeof(address);
    address.sin6_family = AF_INET6;
#else
    struct sockaddr_in address; //创建一个ipv4类型的地址结构体
    bzero(&address, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
#endif
    return [self managerForAddress:&address]; //根据地址信息创建对象
}

其中sockaddr_in6和sockaddr_in是描述网络套接字的结构体,包含协议族类型、端口、ip地址等信息,然后调用managerForAddress:方法创建,代码注释如下:

+ (instancetype)managerForAddress:(const void *)address {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); //创建SCNetworkReachabilityRef对象
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; //创建AFNetworkReachabilityManager对象,初始还参数
    return manager;
}

该方法的核心代码是通过SCNetworkReachabilityCreateWithAddress方法创建一个SCNetworkReachabilityRef对象reachability,该对象负责监听address的网络状态,address参数是需要监听的地址信息,由于address参数的ip地址为空,则reachability对象监听当前设备的网络连接状态。另一个方法managerForDomain:是通过传进来的domain网络地址名,例如www.baidu.com来创建。代码注释如下:

+ (instancetype)managerForDomain:(NSString *)domain {
    //根据domain创建SCNetworkReachabilityRef对象
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; //创建AFNetworkReachabilityManager对象,初始还参数
    return manager;
}

SCNetworkReachabilityCreateWithName方法和上面的类似,也是用于创建一个SCNetworkReachabilityRef对象。调用initWithReachability:方法,初始化参数,代码注释如下:

- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
    self = [super init];
    if (!self) {
        return nil;
    }
    self.networkReachability = CFBridgingRelease(reachability); //初始化networkReachability属性
    self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; //初始化连接状态属性
    return self;
}

networkReachability属性持有reachability,networkReachabilityStatus属性表示当前连接的网络状态,共有以下几种:

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,//未知状态
    AFNetworkReachabilityStatusNotReachable     = 0, //未连接
    AFNetworkReachabilityStatusReachableViaWWAN = 1, //蜂窝移动网络(2G/3G/4G)
    AFNetworkReachabilityStatusReachableViaWiFi = 2, //wifi网络
};

上面的几个枚举值表示当前检测到的网络状态。

监听网络状态

AFNetworkReachabilityManager通过startMonitoring方法和stopMonitoring开始并停止监听当前设备连接的网络状态。

  1. startMonitoring方法

    该方法主要通过SystemConfiguration框架提供的API将networkReachability让对象加入runloop中,开始工作,并且绑定监听的回调函数处理状态改变。代码注释如下:

    - (void)startMonitoring {
        [self stopMonitoring]; //1.停止之前的监听
        if (!self.networkReachability) {
            return;
        }
     //2.创建context的block
        __weak __typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.networkReachabilityStatus = status;
            if (strongSelf.networkReachabilityStatusBlock) {
                strongSelf.networkReachabilityStatusBlock(status);
            }
        };
        id networkReachability = self.networkReachability;
         //创建SCNetworkReachabilityContext对象
        SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
         //3.设置networkReachability的回调函数和context
        SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);
         //4.将networkReachability加入runloop中
        SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    
    //5.获取当前网络连接状态
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
     SCNetworkReachabilityFlags flags;
     if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {
         AFPostReachabilityStatusChange(flags, callback); //执行block,发通知
     }
        });
    }
    

    该方法首先停止之前的监听,然后调用SCNetworkReachabilitySetCallback方法来设置networkReachability的回调函数AFNetworkReachabilityCallback和上下文context对象,该方法的定义和参数如下:

    Boolean
    SCNetworkReachabilitySetCallback     (
     SCNetworkReachabilityRef        target, //networkReachability对象
     SCNetworkReachabilityCallBack   __nullable callout, //回调方法
     SCNetworkReachabilityContext    * __nullable context //上下文兑现
    )
    

    首先target对象是要绑定的networkReachability对象,即networkReachability属性,callout是networkReachability对象监听到网络状态发生改变时,触发的回调函数,类型是SCNetworkReachabilityCallBack,定义如下:

    typedef void (*SCNetworkReachabilityCallBack)    (
     SCNetworkReachabilityRef            target, //networkReachability对象
     SCNetworkReachabilityFlags          flags,  //回调参数,状态值
     void                 *  __nullable  info    //info函数指针
    );
    

    该函数回抛target对象,即之前绑定的networkReachability对象。以及flag标识,通过flag可以获取当前网络状态值。以及info指针,指向一个block。其中info是由context提供的,context是SCNetworkReachabilityContext类型的结构体,定义如下:

    typedef struct {
     CFIndex     version;
     void *      __nullable info; //info函数指针
     const void  * __nonnull (* __nullable retain)(const void *info); //retain函数指针
     void        (* __nullable release)(const void *info); //release函数指针
     CFStringRef __nonnull (* __nullable copyDescription)(const void *info); //获取info的Description的函数指针
    } SCNetworkReachabilityContext;
    

    其中info函数指针提供给上文的回调函数使用,作为回调参数info。info是AFNetworkReachabilityStatusBlock类型,需要一个回调参数status,创建info的代码如下:

    __weak __typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.networkReachabilityStatus = status; //设置状态值
            if (strongSelf.networkReachabilityStatusBlock) { //将状态值通过block回抛给外界
                strongSelf.networkReachabilityStatusBlock(status);
            }
    };
    

    该函数负责将获取到的网络状态值status通过networkReachabilityStatusBlock回抛给外界。retain函数指针和release函数指针也分别设置了。

    callout的方法如下:

    static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
        AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); //调用AFPostReachabilityStatusChange方法
    }
    

    该方法调用AFPostReachabilityStatusChange方法做逻辑处理,注释如下:

    static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
        AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); //根据flags获取当前网络连接状态status
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block(status); //block是context中的info指针,调用info将status传递给外界
            }
             //status作为通知的值,发通知抛给外界
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
            [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
        });
    }
    

    该方法首先根据系统回调的flags参数和AFNetworkReachabilityStatusForFlags方法,获取网络连接状态status,然后调用block,即之前context中的info指针,将status抛给外界。同时抛一个通知将status抛给外界。因此当网络状态改变时,会同时用两种方式传递给外界。

    AFNetworkReachabilityStatusForFlags方法是核心方法,负责根据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) {//当前连接是WWAN
            status = AFNetworkReachabilityStatusReachableViaWWAN;
        }
    #endif
        else { //当前连接是WiFi
            status = AFNetworkReachabilityStatusReachableViaWiFi;
        }
        return status;
    }
    

    最后在startMonitoring方法中还调用了SCNetworkReachabilityGetFlags方法获取当前网络状态,传递给外界。因为之前的设置是在网络状态发生变化时触发的。

  2. stopMonitoring方法

    通知监听的方法是让networkReachability对象从runloop中注销。代码注释如下:

    - (void)stopMonitoring {
        if (!self.networkReachability) {
            return;
        }
        //从runloop中注销networkReachability对象
        SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    }
    

读取网络状态

AFNetworkReachabilityManager维护了一些网络状态属性,如下:

@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;

外界通过isReachable方法、isReachableViaWWAN方法、isReachableViaWiFi方法和networkReachabilityStatus属性可以获取当前的网络状态。同时通过实现keyPathsForValuesAffectingValueForKey:方法设置属性值的依赖关系。当reachable、reachableViaWWAN、reachableViaWiFi这些属性的值发生变化时,会触发networkReachabilityStatus属性的kvo,如果外界通过kvo监听networkReachabilityStatus属性的变化,这时会触发kvo。

总结

AFNetworkReachabilityManager作为一个监听设备网络连接状态的类,核心是使用了systemConfiguration框架中的SCNetworkReachability相关类和API实现的,值得学习和了解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容