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

2019-10-23  本文已影响0人  木小易Ying

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接口有同步和异步两种模式.

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

#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

上一篇 下一篇

猜你喜欢

热点阅读