iOS Developer程序员IOS

iOS源码补完计划--AFNetworking(二)

2018-05-16  本文已影响88人  kirito_song

目录

前言

AFNetworking源码第二篇
主要看了看AFNetworkReachabilityManager的内容
负责在有需要时、对网络状态的获取和监控
作为一个辅助模块、代码量和文件都比较少
一行一行读下来就可以了

AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》

AFNetworkReachabilityManager.h

我们平时使用的枚举、就是这四个。

//网络状态
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,//未知
    AFNetworkReachabilityStatusNotReachable     = 0,//无网络
    AFNetworkReachabilityStatusReachableViaWWAN = 1,//运营商网络
    AFNetworkReachabilityStatusReachableViaWiFi = 2,//WiFi网络
};

也可以单独判断是否在某个环境下
均为只读

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

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

/**
    当前是否为运营商网络
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
    当前是否为WiFi
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
/**
    全局单例
 */
+ (instancetype)sharedManager;

/**
    工厂方法
 */
+ (instancetype)manager;

/**
    监控指定网址的管理器
 */
+ (instancetype)managerForDomain:(NSString *)domain;

/**
    监听某个IP的管理器
 */
+ (instancetype)managerForAddress:(const void *)address;

/**
    通过SCNetworkReachabilityRef对象初始化、其余所有的方法也会汇聚于此
 */
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;

最后一个initWithReachability:是推荐的方法、因为其他所有的方法最后内部都是通过它初始化

/**
    打开监听
 */
- (void)startMonitoring;

/**
    关闭监听
 */
- (void)stopMonitoring;

大概是方便开发者获取并且直接提示给用户吧

- (NSString *)localizedNetworkReachabilityStatusString;

1、通过block:

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

2、通过监听

FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;

关于FOUNDATION_EXPORTUIKIT_EXTERN的选择:
有人说是如果文件基于FOUNDATION则用前者、反之则用后者。
二者都能替代#define、并且通过地址比对常量(也就是可以通过 == 直接进行比较)、效率更高。


AFNetworkReachabilityManager.m

所有的初始化最后都归结于这里:用SCNetworkReachabilityRef对象初始化

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

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

    return self;
}

基于它的两个工厂方法

+ (instancetype)managerForDomain:(NSString *)domain {
    
    /*
         SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName (     //根据传入的网址创建网络连接引用
         CFAllocatorRef allocator,                  //可以为NULL或kCFAllocatorDefault
         const char *nodename                       //比如为"www.baidu.com",此参数为域名
     
         );
         这个是根据传入的网址测试连接,
         第一个参数 可以为NULL或kCFAllocatorDefault,
         第二个参数比如为"www.apple.com",其他和上一个一样。
     */
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);

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

    return manager;
}

+ (instancetype)managerForAddress:(const void *)address {
    /*
         SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress (    //根据传入的地址创建网络连接引用
         CFAllocatorRef allocator,                  //可以为NULL或kCFAllocatorDefault
         const struct sockaddr *address            //需要测试连接的IP地址
     
         );
         第一个参数 可以为NULL或kCFAllocatorDefault,
         第二个参数 为需要测试连接的IP地址,当为0.0.0.0时则可以查询本机的网络连接状态。
     */
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    CFRelease(reachability);
    
    return manager;
}

需要注意的是初始化所用的SCNetworkReachabilityRef引用、必须在使用后CFRelease()

+ (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];
}

这个似乎涉及到了socket、等我研究研究补上~
再有就是关于#if - #esle - #endif
其实用普通的if-else也是一样、好处就是在编译阶段是否会被编译。
不过、#if - #esle - #endif不能用来判断一个动态的语法。

- (void)startMonitoring {
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }

    
    __weak __typeof(self)weakSelf = self;
    //网络状态改变的时候、执行这个block
    //为什么这么写?因为AFPostReachabilityStatusChange会返回我们需要的status、但必须要一个block接受
    //并没什么特殊的含义、单纯的方便接受status罢了
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        //改变manager的网络状态枚举
        strongSelf.networkReachabilityStatus = status;
        //调用用户传入的block
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
    };

    /*
         typedef struct {
         //接受一个signed long 的参数
         CFIndex        version;
         //接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数
         void *        __nullable info;
         //接受一个函数 目的是对info做retain操作,
         const void    * __nonnull (* __nullable retain)(const void *info);
         //接受一个函数,目的是对info做release操作
         void        (* __nullable release)(const void *info);
         //接受一个函数,根据info获取Description字符串
         CFStringRef    __nonnull (* __nullable copyDescription)(const void *info);
         } SCNetworkReachabilityContext;
     
         对于这些参数、都不是必须传一个block或者怎样。如果info不是block、那么后面的reatin啥的都可以忽略
         SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
     */
    //新建上下文
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    
    //设置回调 -- 这里 AFNetworkReachabilityCallback 可以上面的info、也就是callback那个block 进行处理
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    //放入RunLoop池
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            //网络状态改变--去获取当前网络状态。然后发通知、调用block
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

我对SCNetworkReachability没啥研究、只能一遍查资料一遍顺便注释一下、至少保证上面的代码能看懂。

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);
    // 是否可以自动联网 => 条件: 1.存在网络 2.可以自动尝试并完成连接
    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 {
        
        //WiFi
        status = AFNetworkReachabilityStatusReachableViaWiFi;
    }

    return status;
}

关于SCNetworkReachabilityFlags枚举

typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
      // 可以通过瞬时连接(例如PPP---点对点通讯协议) 链接到给定的节点名和地址
    kSCNetworkReachabilityFlagsTransientConnection  = 1<<0,
        //能够连接网络
    kSCNetworkReachabilityFlagsReachable        = 1<<1, 
        //能够连接网络、但是首先得建立连接过程
    kSCNetworkReachabilityFlagsConnectionRequired   = 1<<2, = 1<<3,
    //当前网络配置可以请求到给定的节点名和地址,但必须先通过确认用户创建一个连接。
用户需要通过某些方式的介入来创建该连接,比如提供密码、认证口令等。一般的,只有在一个拨号通信的配置中,在自动连接过程中发生某些错误(比如没有拨号音,没有回应,无效的密码等),这种情况下,PPP通信将会终止连接直到用户介入。
    kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
    //当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。该请求一经CFSocketStream程序接口请求就创建,其他的函数不会创建该连接
    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
};

KVO的一个冷门方法

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

    return [super keyPathsForValuesAffectingValueForKey:key];
}

return的 值被改变的时候、触发key的监听
也就是说当networkReachabilityStatus改变的时候、reachable/reachableViaWWAN/reachableViaWiFi的KVO监听都将被触发


API注释Demo

把注释的源码放在了github上、有兴趣可以自取。

GitHub


参考资料

AFNetworking 3.0 源码解读(一)AFNetworkReachabilityManager
SCNetworkReachability 说明使用
提升自己逼格的编程之美之代码规范
iOS 判断网络连接状态之重写Reachability
IOS SCNetworkReachability和Reachability监测网络连接状态
iOS开发,#define的使用(系列一)
iOS概念之KVO(Key-Value Observing

上一篇 下一篇

猜你喜欢

热点阅读