美文网首页
[iOS] AFNetworking源码学习—网络监听

[iOS] AFNetworking源码学习—网络监听

作者: 木小易Ying | 来源:发表于2019-10-23 22:52 被阅读0次

    AFNetworking通过AFNetworkReachabilityManager可以监听网络状态,它的原理是通过SCNetworkReachabilityRef来获取网络可达性。

    1. 创建测试连接 SCNetworkReachabilityRef

    所以我们先来看下SCNetworkReachabilityRef是什么:

    typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;
    

    SCNetworkReachabilityRef其实就是一个struct,可以确定当前主机的网络状态以及目标主机的可达性。

    它提供三种初始化方式:

    /**
     初始化方式一:
     从这个接口可以得知,需要两个传入的参数,
     所有需要对这两个参数进行初始化,传入接口中。
    
     @param allocator 推荐使用默认 kCFAllocatorDefault
     @param address 套接字的地址结构
     @return SCNetworkReachabilityRef 这个类的实例
     */
    SCNetworkReachabilityRef __nullable
    SCNetworkReachabilityCreateWithAddress        (
                                                   CFAllocatorRef            __nullable    allocator, //创建对指定网络的引用地址。 此引用可以在以后用于监视目标主机的可达性。
                                                   const struct sockaddr                *address // 本地主机地址
                                                   )                API_AVAILABLE(macos(10.3), ios(2.0));
    
    // 初始化方式二:
    SCNetworkReachabilityRef __nullable
    SCNetworkReachabilityCreateWithName        (
                                                CFAllocatorRef            __nullable    allocator,
                                                const char                    *nodename // 域名或者ip地址
                                                )                API_AVAILABLE(macos(10.3), ios(2.0));
    // 初始化方式三:
    SCNetworkReachabilityRef __nullable
    SCNetworkReachabilityCreateWithAddressPair    (
                                                   CFAllocatorRef            __nullable    allocator,
                                                   const struct sockaddr        * __nullable    localAddress,
                                                   const struct sockaddr        * __nullable    remoteAddress
                                                   )   
    

    AFNetworking中初始化的方式如下:

    // 通过域名
    + (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;
    }
    

    AFNetworkReachabilityManager的实例变量有一个是@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability用于保存SCNetworkReachabilityRef

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

    注意SCNetworkReachabilityRef是Core Foundation的对象,是由C语言实现的,需要手动管理计数,所以赋值给实例变量的时候需要先retain再赋值,而局部变量在已经赋值给实例变量以后则可以release了。

    需要注意,dealloc的时候要release _networkReachability哦 :

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

    SCNetworkReachabilityRef接口有同步和异步两种模式.

    • 在同步模式下, 可以通过SCNetworkReachabilityGetFlags方法获取网络状态;
    • 在异步模式下, 可以调度一个SCNetworkReachability对象到客户端的RunLoop上, 客户端实现一个callback接收网络状态变化的通知, 这个回调函数遵循Core Foundation命名规范, 只要函数名中包含 "Create" 或 "Copy"的函数返回的引用,都必须调用[CFRelease]来释放。

    先来举个同步获取的例子:

    #import <SystemConfiguration/SystemConfiguration.h>
    #import <Foundation/Foundation.h>
    #import <netinet/in.h>
    #import <netinet6/in6.h>
    #import <arpa/inet.h>
    #import <ifaddrs.h>
    #import <netdb.h>
    
    - (BOOL)isConnectionAvailable {
        //创建零地址,0.0.0.0的地址表示查询本机的网络连接状态
        struct sockaddr_in zeroAddress;
        bzero(&zeroAddress, sizeof(zeroAddress));
        zeroAddress.sin_len = sizeof(zeroAddress);
        zeroAddress.sin_family = AF_INET;
        
        // Recover reachability flags
        SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
        SCNetworkReachabilityFlags flags;
        //获得连接的标志
        BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
        CFRelease(defaultRouteReachability);
        //如果不能获取连接标志,则不能连接网络,直接返回
        if (!didRetrieveFlags)
        {
            NSLog(@"Error. Could not recover network reachability flags");
            return NO;
        }
        //根据获得的连接标志进行判断
        BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);
        BOOL needsConnection = ((flags & kSCNetworkFlagsConnectionRequired) != 0);
        return (isReachable && !needsConnection) ? YES : NO;
    }
    

    然后看下AFNetworkReachabilityManager的初始化:

    + (instancetype)sharedManager {
        static AFNetworkReachabilityManager *_sharedManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _sharedManager = [self manager];
        });
    
        return _sharedManager;
    }
    
    + (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];
    }
    

    可以看到它是通过建立0.0.0.0的address来测试本机网络状况滴。

    注意这里通过版本区分了IPV6和IPV4,如果是高版本就用sockaddr_in6:

    /* Structure describing a generic socket address.  */
    struct sockaddr
    {
     uint16 sa_family;           /* Common data: address family and length.  */
     char sa_data[14];           /* Address data.  */
    };
    
    /* Structure describing an Internet socket address.  */
    struct sockaddr_in
    {
     uint16 sin_family;          /* Address family AF_INET */ 
     uint16 sin_port;            /* Port number.  */
     uint32 sin_addr.s_addr;     /* Internet address.  */
     unsigned char sin_zero[8];  /* Pad to size of `struct sockaddr'.  */
    };
    
    /* Ditto, for IPv6.  */
    struct sockaddr_in6
    {
     uint16 sin6_family;         /* Address family AF_INET6 */
     uint16 sin6_port;           /* Transport layer port # */
     uint32 sin6_flowinfo;       /* IPv6 flow information */
     uint8  sin6_addr[16];       /* IPv6 address */
     uint32 sin6_scope_id;       /* IPv6 scope-id */
    };
    

    2. 异步测试连接 - SCNetworkReachabilityContext & SCNetworkReachabilityCallBack

    用下面的方式可以加异步监测连接callback:

    Boolean SCNetworkReachabilitySetCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callout, SCNetworkReachabilityContext *context);
    

    当网络状态发生变化时, 就会调用callout, 第一个参数是网络连接引用, 第二个参数是回调, 如果为NULL, 当前的target就会被移除, SCNetworkReachabilityCallBack的类型为(typedef void (*SCNetworkReachabilityCallBack)(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);) callout回调中的info参数就是从第三个参数context结构体中取的info回调, 这样就把结构体context中的数据传到了SCNetworkReachabilityCallBack参数中, 第三个参数是与callout相关联的上下文, 可能为空。如果通知客户端成功就返回true, 否则返回false.

    SCNetworkReachabilityContext有是什么呢?

    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;
    
    1. 第一个参数接受一个signed long 的参数,version为版本号, 作为参数传递给SCDynamicStore创建结构体类型的版本号
    2. 第二个参数接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数
    3. 第三个参数 是一个函数,目的是对info做retain操作,
    4. 第四个参数是一个函数,目的是对info做release操作
    5. 第五个参数是 一个函数,根据info获取Description字符串
    

    我们看到AF中创建context是酱紫的:

    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    
    static const void * AFNetworkReachabilityRetainCallback(const void *info) {
        return Block_copy(info);
    }
    
    static void AFNetworkReachabilityReleaseCallback(const void *info) {
        if (info) {
            Block_release(info);
        }
    }
    

    这里的info就是:

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
        ……
        return strongSelf;
    };
    

    SCNetworkReachabilityCallBack
    监听的callback是酱紫的:

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

    这里并没有用SCNetworkReachabilityCallBack是为什么嘞,其实SCNetworkReachabilityCallBack的结构和AFNetworkReachabilityCallback定义是一致的:

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

    callback里面转调了AFPostReachabilityStatusChange:

    static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusCallback block) {
        AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
        dispatch_async(dispatch_get_main_queue(), ^{
            AFNetworkReachabilityManager *manager = nil;
            if (block) {
                manager = block(status);
            }
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
            [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:manager userInfo:userInfo];
        });
    }
    

    因为info本来type就是AFNetworkReachabilityStatusCallback所以这里直接转换后再传入参数。

    在AFPostReachabilityStatusChange里面先解析转换了flags,将它换为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;
    }
    

    然后发送了AFNetworkingReachabilityDidChangeNotification通知,将state放入userinfo,object设为manager。

    解析完state以后manager = block(status)做了什么其实就是最开始的info:

    AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
    
        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
        
        return strongSelf;
    };
    

    也就是将manager的网络状态设置一下,并且调用manager的networkReachabilityStatusBlock。

    • 总结一下检查网络状态的流程:
    1. 通过SCNetworkReachabilitySetCallback设置网络状态传递给callback函数
    2. callback中调用AFPostReachabilityStatusChange
    3. AFPostReachabilityStatusChange中解析网络state,并且发送notification,以及调用SCNetworkReachabilitySetCallback设置时传入的info
    4. info接收传入的state,将manager的state修正,并且调用manager的networkReachabilityStatusBlock

    这里有一个小问题,为什么要让callback转调AFPostReachabilityStatusChange,直接callback处理state以后调用info不可以么?

    其实是因为AFPostReachabilityStatusChange不仅这一个地方要用,在设置了异步获取网络状态以后,紧接着也抛了一个同步获取了一下,如果直接在callback里面写处理获取结果的逻辑,那么同步获取以后还要再写一遍,于是为了复用就加了一个方法吧。

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

    3. 一直监听网络状态 - SCNetworkReachabilityScheduleWithRunLoop

    通过SCNetworkReachabilityScheduleWithRunLoop可以将一个SCNetworkReachabilityRef放入runloop,当SCNetworkReachabilityRef状态变化的时候,调用SCNetworkReachabilitySetCallback中设置的callback。

    SCNetworkReachabilityScheduleWithRunLoop的用法:

    Boolean
    SCNetworkReachabilityScheduleWithRunLoop    (
                            SCNetworkReachabilityRef    target,
                            CFRunLoopRef            runLoop,
                            CFStringRef         runLoopMode
                            )               API_AVAILABLE(macos(10.3), ios(2.0));
    

    target加入指定的runloop指定的Mode中, 会一直监测target的网络状态,当网络状态发生变化时就会执行SCNetworkReachabilitySetCallback方法中的callout回调。

    我猜测它的实现大概是给runLoop加observer,当runloop要进入休眠/开始的时候去尝试SCNetworkReachabilityRef的连接,如果发生了状态变化就调用callout。

    4. 使用AFNetworkReachabilityManager监测网络状态

    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusReachableViaWWAN:
                NSLog(@"蜂窝网络");
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"WIFI");
                break;
            case AFNetworkReachabilityStatusNotReachable:
                NSLog(@"没有网络");
                break;
            case AFNetworkReachabilityStatusUnknown:
                NSLog(@"未知");
                break;
            default:
                break;
        }
    }];
    

    5. 小细节 - 如何将一些属性的KVO和其他属性关联

    manager有几个属性:

    /**
     Whether or not the network is currently reachable.
     */
    @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
    
    /**
     Whether or not the network is currently reachable via WWAN.
     */
    @property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
    
    /**
     Whether or not the network is currently reachable via WiFi.
     */
    @property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
    

    但是这三个属性其实内部都是通过state算出来的:

    - (BOOL)isReachable {
        return [self isReachableViaWWAN] || [self isReachableViaWiFi];
    }
    
    - (BOOL)isReachableViaWWAN {
        return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
    }
    
    - (BOOL)isReachableViaWiFi {
        return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
    }
    

    所以其实networkReachabilityStatus变化的时候应该通知监听了isReachable等的对象,所以通过keyPathsForValuesAffectingValueForKey可以建立两个属性的关联,当返回的NSSet中的某个属性变化了,会通知监听了key属性的对象。
    (注意这个是类方法哦~)

    #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];
    }
    

    参考:
    https://www.jianshu.com/p/45bb83a715d0
    https://www.jianshu.com/p/6ec618163963
    https://www.jianshu.com/p/5d1cfad96cba
    https://www.cnblogs.com/machao/p/5681645.htmljian
    https://blog.csdn.net/albertsh/article/details/80991684

    相关文章

      网友评论

          本文标题:[iOS] AFNetworking源码学习—网络监听

          本文链接:https://www.haomeiwen.com/subject/icxlvctx.html