AFNetworkReachabilityManager 监控网络状态(四)

sy7118 5年前
   <p>AFNetworkReachabilityManager 是对 SystemConfiguration 模块的封装,苹果的文档中也有一个类似的项目 <a href="/misc/goto?guid=4959670641997165247" rel="nofollow,noindex">Reachability</a> 这里对网络状态的监控跟苹果官方的实现几乎是完全相同的。</p>    <p>同样在 github 上有一个类似的项目叫做 <a href="/misc/goto?guid=4958870674954195570" rel="nofollow,noindex">Reachability</a> 不过这个项目 <strong>由于命名的原因可能会在审核时被拒绝</strong> 。</p>    <p>无论是 AFNetworkReachabilityManager ,苹果官方的项目或者说 github 上的 Reachability,它们的实现都是类似的,而在这里我们会以 AFNetworking 中的 AFNetworkReachabilityManager 为例来说明在 iOS 开发中,我们是怎样监控网络状态的。</p>    <h2>AFNetworkReachabilityManager 的使用和实现</h2>    <p>AFNetworkReachabilityManager 的使用还是非常简单的,只需要三个步骤,就基本可以完成对网络状态的监控。</p>    <ol>     <li><a href="/misc/goto?guid=4958346284570877790" rel="nofollow,noindex">初始化 AFNetworkReachabilityManager </a></li>     <li><a href="/misc/goto?guid=4958346284570877790" rel="nofollow,noindex">调用 startMonitoring 方法开始对网络状态进行监控 </a></li>     <li><a href="/misc/goto?guid=4958346284570877790" rel="nofollow,noindex">设置 networkReachabilityStatusBlock 在每次网络状态改变时, 调用这个 block </a></li>    </ol>    <h3>初始化 AFNetworkReachabilityManager</h3>    <p>在初始化方法中,使用 SCNetworkReachabilityCreateWithAddress 或者 SCNetworkReachabilityCreateWithName 生成一个 SCNetworkReachabilityRef 的引用。</p>    <pre>  + (instancetype)managerForDomain:(NSString *)domain {      SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);        AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];        return manager;  }    + (instancetype)managerForAddress:(const void *)address {      SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);      AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];        return manager;  }  </pre>    <ol>     <li>这两个方法会通过一个 <strong>域名</strong> 或者一个 sockaddr_in 的指针生成一个 SCNetworkReachabilityRef</li>     <li>调用 - [AFNetworkReachabilityManager initWithReachability:] 将生成的 SCNetworkReachabilityRef 引用传给 networkReachability</li>     <li>设置一个默认的 networkReachabilityStatus</li>    </ol>    <pre>  - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {      self = [super init];      if (!self) {          return nil;      }        self.networkReachability = CFBridgingRelease(reachability);      self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;        return self;  }  </pre>    <p>当调用 CFBridgingRelease(reachability) 后,会把 reachability 桥接成一个 NSObject 对象赋值给 self.networkReachability ,然后释放原来的 CoreFoundation 对象。</p>    <h3>监控网络状态</h3>    <p>在初始化 AFNetworkReachabilityManager 后,会调用 startMonitoring 方法开始监控网络状态。</p>    <pre>  - (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);          }        };        id networkReachability = self.networkReachability;      SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};      SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);      SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{          SCNetworkReachabilityFlags flags;          if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {              AFPostReachabilityStatusChange(flags, callback);          }      });  }  </pre>    <ol>     <li> <p>先调用 - stopMonitoring 方法,如果之前设置过对网络状态的监听,使用 SCNetworkReachabilityUnscheduleFromRunLoop 方法取消之前在 Main Runloop 中的监听</p> <pre>  - (void)stopMonitoring {      if (!self.networkReachability) {          return;      }      </pre> <pre>  SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);  </pre> }</li>     <li> <p>创建一个在每次网络状态改变时的回调</p> <pre>  __weak __typeof(self)weakSelf = self;  AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {      __strong __typeof(weakSelf)strongSelf = weakSelf;      </pre> <pre>  strongSelf.networkReachabilityStatus = status;  if (strongSelf.networkReachabilityStatusBlock) {      strongSelf.networkReachabilityStatusBlock(status);  }  </pre> <p>};</p>      <ul>       <li>每次回调被调用时        <ul>         <li>重新设置 networkReachabilityStatus 属性</li>         <li>调用 networkReachabilityStatusBlock</li>        </ul> </li>      </ul> </li>     <li> <p>创建一个 SCNetworkReachabilityContext</p> <pre>  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;      SCNetworkReachabilityContext context = {      0,      (__bridge void *)callback,      AFNetworkReachabilityRetainCallback,       AFNetworkReachabilityReleaseCallback,       NULL  };  </pre>      <ul>       <li>其中的 callback 就是上一步中的创建的 block 对象</li>       <li>这里的 AFNetworkReachabilityRetainCallback 和 AFNetworkReachabilityReleaseCallback 都是非常简单的 block,在回调被调用时,只是使用 Block_copy 和 Block_release 这样的宏</li>       <li> <p>传入的 info 会以参数的形式在 AFNetworkReachabilityCallback 执行时传入</p> <p>static const void * AFNetworkReachabilityRetainCallback(const void *info) { return Block_copy(info); }</p> <p>static void AFNetworkReachabilityReleaseCallback(const void *info) { if (info) { Block_release(info); } }</p> </li>      </ul> </li>     <li> <p>当目标的网络状态改变时,会调用传入的回调</p> <pre>  SCNetworkReachabilitySetCallback(      (__bridge SCNetworkReachabilityRef)networkReachability,      AFNetworkReachabilityCallback,       &context  );  </pre> </li>     <li> <p>在 Main Runloop 中对应的模式开始监控网络状态</p> <pre>  SCNetworkReachabilityScheduleWithRunLoop(      (__bridge SCNetworkReachabilityRef)networkReachability,       CFRunLoopGetMain(),       kCFRunLoopCommonModes  );  </pre> </li>     <li> <p>获取当前的网络状态,调用 callback</p> <pre>  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{      SCNetworkReachabilityFlags flags;      if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {          AFPostReachabilityStatusChange(flags, callback);      }  });  </pre> </li>    </ol>    <p>在下一节中会介绍上面所提到的一些 C 函数以及各种回调。</p>    <h3>设置 networkReachabilityStatusBlock 以及回调</h3>    <p>在 Main Runloop 中对网络状态进行监控之后,在每次网络状态改变,就会调用 AFNetworkReachabilityCallback 函数:</p>    <pre>  static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {        AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);  }  </pre>    <p>这里会从 info 中取出之前存在 context 中的 AFNetworkReachabilityStatusBlock 。</p>    <pre>  __weak __typeof(self)weakSelf = self;    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {        __strong __typeof(weakSelf)strongSelf = weakSelf;        strongSelf.networkReachabilityStatus = status;      if (strongSelf.networkReachabilityStatusBlock) {          strongSelf.networkReachabilityStatusBlock(status);      }    };  </pre>    <p>取出这个 block 之后,传入 AFPostReachabilityStatusChange 函数:</p>    <pre>  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];      });  }  </pre>    <ol>     <li>调用 AFNetworkReachabilityStatusForFlags 获取当前的网络可达性状态</li>     <li><strong>在主线程中异步执行</strong> 上面传入的 callback block(设置 self 的网络状态,调用 networkReachabilityStatusBlock )</li>     <li>发送 AFNetworkingReachabilityDidChangeNotification 通知.</li>    </ol>    <pre>  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;  }  </pre>    <p>因为 flags 是一个 SCNetworkReachabilityFlags ,它的不同位代表了不同的网络可达性状态,通过 flags 的位操作,获取当前的状态信息 AFNetworkReachabilityStatus 。</p>    <pre>  typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {        kSCNetworkReachabilityFlagsTransientConnection  = 1<<0,      kSCNetworkReachabilityFlagsReachable        = 1<<1,      kSCNetworkReachabilityFlagsConnectionRequired   = 1<<2,      kSCNetworkReachabilityFlagsConnectionOnTraffic  = 1<<3,      kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,      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  };  </pre>    <p>这里就是在 SystemConfiguration 中定义的全部的网络状态的标志位。</p>    <h2>与 AFNetworking 协作</h2>    <p>其实这个类与 AFNetworking 整个框架并没有太多的耦合。正相反,它在整个框架中作为一个 <strong>即插即用</strong> 的类使用,每一个 AFURLSessionManager 都会持有一个 AFNetworkReachabilityManager 的实例。</p>    <pre>  self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];    </pre>    <p>这是整个框架中除了 AFNetworkReachabilityManager.h/m 文件, <strong>唯一一个</strong> 引用到这个类的地方。</p>    <p>在实际的使用中,我们也可以直接操作 AFURLSessionManager 的 reachabilityManager 来获取当前的网络可达性状态,而不是自己手动初始化一个实例,当然这么做也是没有任何问题的。</p>    <h2>总结</h2>    <ol>     <li>AFNetworkReachabilityManager 实际上只是一个对底层 SystemConfiguration 库中的 C 函数封装的类,它为我们隐藏了 C 语言的实现,提供了统一的 Objective-C 语言接口</li>     <li>它是 AFNetworking 中一个即插即用的模块</li>    </ol>    <h2>来自: <a href="/misc/goto?guid=4959670642151466194" rel="nofollow">http://draveness.me/afnetworking4/</a></h2>