美文网首页
AFNetworking之AFNetworkReachabili

AFNetworking之AFNetworkReachabili

作者: 我一不小心就 | 来源:发表于2018-04-13 18:08 被阅读0次

    储备知识:FOUNDATION_EXPORT 和#define 都能定义常量。FOUNDATION_EXPORT 能够使用==进行判断,效率略高。而且能够隐藏定义细节(就是实现部分不在.中)

    前言:AFNetworking主要由6个部分组成:AFURLRequestSerialization、AFURLResponseSerialization、AFSecurityPolicy、AFNetworkReachabilityManager、AFURLSessionManager、AFHTTPSessionManager。然后其中比较独立的有四个AFURLRequestSerialization、AFURLResponseSerialization、AFSecurityPolicy、AFNetworkReachabilityManager。

    首先介绍AFNetworkReachabilityManager.h的相关内容。

    #import <SystemConfiguration/SystemConfiguration.h>
    

    这个头文件说明网络监控的实现是依赖SystemConfiguration这个api。

    /*
       网络类型 (网络状态的枚举)
     */
    typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
        /*
            未知
         */
        AFNetworkReachabilityStatusUnknown          = -1,
        /*
            无网络
         */
        AFNetworkReachabilityStatusNotReachable     = 0,
        /*
             wwan 手机自带网络
         */
        AFNetworkReachabilityStatusReachableViaWWAN = 1,
        /*
            WiFi
         */
        AFNetworkReachabilityStatusReachableViaWiFi = 2,
    };
    

    如果此种的枚举类型不能满足我们的需求,可以自行扩展

    NS_ASSUME_NONNULL_BEGIN
    NS_ASSUME_NONNULL_END
    

    这个是为了swift的可选类型配添加的,在这两个终点的内容的参数默认都是nonnull的。

    // 当前网络状态
    @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方法的。

    + (instancetype)sharedManager;
    
    + (instancetype)manager;
    
    + (instancetype)managerForDomain:(NSString *)domain;
    
    + (instancetype)managerForAddress:(const void *)address;
    
    - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
    

    默认提供的初始化方法.
    SCNetworkReachabilityRef 这个很重要,这个类的就是基于它开发的。

    + (instancetype)managerForDomain:(NSString *)domain;
    + (instancetype)managerForAddress:(const void *)address;
    

    managerForDomain:监听制定domain的网络状态,managerForAddress:监听某个socket地址的网络状态.

    /**
     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;
    
    

    返回当前网络可达状态的本地化字符串表示形式。返回一个网络状态的本地语言的字符串。往往我们可以根据这个字符串来告诉用户,当前网络发生了什么,当然,也可以根据状态自定义提示文字。

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

    监听网络状态发生改变时的回调。
    1.使用上边的这个方法。

    2.监听AFNetworkingReachabilityDidChangeNotification通知。

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

    这个是与网络状态变化相关的通知。接受的通知中会有一个userinfo 是一个NSDictionary 其中key就是

    AFNetworkingReachabilityNotificationStatusItem.我们平时的开发中 但凡设计到发通知的功能,我们应该把通知的字符串封装到一个专有的文件中,同时在文件内部按不同模块进行区分,当然必要的注释也很有必要。

    FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
    

    对函数:根据状态获取字符串 声明

    ///--------------------------------------------------------------------------------
    ///@紧接着关于AFNetworkReachabilityManager.m的内容
    ///---------------------------------------------------------------------------------

    #import <netinet/in.h>
    #import <netinet6/in6.h>
    #import <arpa/inet.h>
    #import <ifaddrs.h>
    #import <netdb.h>
    

    几个头文件是系统库,是为了后边的 sockaddr_in6 / sockaddr_in .

    //  网络环境改变
    NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
    //  网络环境发生变化,会发送一个通知,同时携带一组状态数据,根据这个key去查找网络status
    NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
    // 定义一个网络状态的回调
    typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
    
    // 将枚举的网络状态转换成本地化以后的字符串
    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);
        }
    }
    
    /* 
    根据SCNetworkReachabilityFlags这个网络标记来转换成我们在开发中经常使用的网络状态
     转换之后将最终结果赋值给枚举类型的AFNetworkReachabilityStatus。
    AFNetworkReachabilityStatus的枚举值如下:
     1.不能连接网络
     2.蜂窝连接
     3.WiFi连接
     4.未知连接
    */
    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 {
            status = AFNetworkReachabilityStatusReachableViaWiFi;
        }
    
        return status;
    }
    

    学习点:很多框架中都会把一个类中的私有方法写成static void funcName(),这样的c函数.
    原因如下:

    1. 在文件的最前方,比较容易查找
    2. 可以适当的使用内联函数,提高效率。
    /**
     * 为主线程排队状态更改通知
     *
    这样做是为了确保以相同的顺序接收通知。
    当他们被派来。如果通知是直接发送的,那么有可能
    队列通知(用于较早状态条件)在处理后
    稍后的更新,导致侦听器处于错误的状态。
     */
    // 根据一个标识 来处理Block和通知。保证两者同一状态。
    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 const void * AFNetworkReachabilityRetainCallback(const void *info) {
        return Block_copy(info);
    }
    
    static void AFNetworkReachabilityReleaseCallback(const void *info) {
        if (info) {
            Block_release(info);
        }
    }
    

    以下是类中的属性

    @interface AFNetworkReachabilityManager ()
    @property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
    @property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
    @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
    @end
    

    // 单例的方法

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

    此处的CFRelease()对应初始化方法中的CFRetain()

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

    首先新建 SCNetworkReachabilityRef 对象,然后调用initWithReachability: 方法。记得手动管理内存(CFRelease与CFRetain)。
    通过上面可以发现SCNetworkReachabilityRef 有两个创建方法:

    1. SCNetworkReachabilityCreateWithName

    2. SCNetworkReachabilityCreateWithAddress

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

    由于IPv6 是ios9和os_x 10.11后边推出的,所有要进行版本判断.此处里面主要涉及到宏判断的使用。通过这段代码我们能学到什么呢?

    1,方法的创建也是有顺序的,可以使用函数访问函数的思想。

    1. if 这样的预编译指令能够替换掉代码中部分if else 。好处就是代码会不会被编译的区别。

    // 初始化方法

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

    上面是对象的释放

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

    .h文件暴露出来的3个BOOL 属性的getter方法,注意,由于我们在@property中定义了getter方法,所以getter方法就要写成我们定义的那种。从这3个方法中也能看出,

    函数嵌套的思想还是很重要,要想做到这一点,只能是多想才行。

    - (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);
            }
        });
    }
    

    这个类的核心方法,设置监听网咯监听。
    SCNetworkReachabilityContext点进去,会发现这是一个结构体,一般c语言的结构体是对要保存的数据的一种描述。
    关于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 的参数
    2. 第二个参数接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数
    3. 第三个参数 是一个函数 目的是对info做retain操作,
    4. 第四个参数是一个函数,目的是对info做release操作
    5. 第五个参数是 一个函数,根据info获取Description字符串

    在这里我们要携带的这个info就是下边的这个block

    __weak __typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.networkReachabilityStatus = status;
            if (strongSelf.networkReachabilityStatusBlock) {
                strongSelf.networkReachabilityStatusBlock(status);
            }
        };
    

    retain和release 函数是下边的这两个函数

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

    设置网络监控分为下边几个步骤:(重点)

    1.我们先新建上下文

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

    2.设置回调

        SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    
    typedef void (*SCNetworkReachabilityCallBack)    (
                            SCNetworkReachabilityRef            target,
                            SCNetworkReachabilityFlags            flags,
                            void                 *    __nullable    info
                            );
    
    static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
         AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
     }
    

    3.加入RunLoop池,其中CFRunLoopGetMain()代表主RunLoop

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

    4.在异步线程中发送当前的网络状态

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

    停止网络监控

    - (void)stopMonitoring {
        if (!self.networkReachability) {
            return;
        }
        SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    }
    

    下面是属性的set以及get方法

    #pragma mark -
    - (NSString *)localizedNetworkReachabilityStatusString {
        return AFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus);
    }
    #pragma mark -
    - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block {
        self.networkReachabilityStatusBlock = block;
    }
    

    KVO注册键值依赖,具体可以百度,监听card的info属性,当user中的name或者age的值发生改变的时候,就会触发info的键值监听方法。这就是键值依赖的作用。

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

    引用文章:AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

    相关文章

      网友评论

          本文标题:AFNetworking之AFNetworkReachabili

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