AFNetworkReachabilityManager 監控網絡狀態(四)

sy7118 8年前發布 | 11K 次閱讀 iOS開發 移動開發 AFNetworking

AFNetworkReachabilityManager 是對 SystemConfiguration 模塊的封裝,蘋果的文檔中也有一個類似的項目 Reachability 這里對網絡狀態的監控跟蘋果官方的實現幾乎是完全相同的。

同樣在 github 上有一個類似的項目叫做 Reachability 不過這個項目 由于命名的原因可能會在審核時被拒絕

無論是 AFNetworkReachabilityManager ,蘋果官方的項目或者說 github 上的 Reachability,它們的實現都是類似的,而在這里我們會以 AFNetworking 中的 AFNetworkReachabilityManager 為例來說明在 iOS 開發中,我們是怎樣監控網絡狀態的。

AFNetworkReachabilityManager 的使用和實現

AFNetworkReachabilityManager 的使用還是非常簡單的,只需要三個步驟,就基本可以完成對網絡狀態的監控。

  1. 初始化 AFNetworkReachabilityManager
  2. 調用 startMonitoring 方法開始對網絡狀態進行監控
  3. 設置 networkReachabilityStatusBlock 在每次網絡狀態改變時, 調用這個 block

初始化 AFNetworkReachabilityManager

在初始化方法中,使用 SCNetworkReachabilityCreateWithAddress 或者 SCNetworkReachabilityCreateWithName 生成一個 SCNetworkReachabilityRef 的引用。

  • (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>

    1. 這兩個方法會通過一個 域名 或者一個 sockaddr_in 的指針生成一個 SCNetworkReachabilityRef
    2. 調用 - [AFNetworkReachabilityManager initWithReachability:] 將生成的 SCNetworkReachabilityRef 引用傳給 networkReachability
    3. 設置一個默認的 networkReachabilityStatus

  • (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability { self = [super init]; if (!self) {

      return nil;
    

    }

    self.networkReachability = CFBridgingRelease(reachability); self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;

    return self; } </pre>

    當調用 CFBridgingRelease(reachability) 后,會把 reachability 橋接成一個 NSObject 對象賦值給 self.networkReachability ,然后釋放原來的 CoreFoundation 對象。

    監控網絡狀態

    在初始化 AFNetworkReachabilityManager 后,會調用 startMonitoring 方法開始監控網絡狀態。

  • (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>

    1. 先調用 - stopMonitoring 方法,如果之前設置過對網絡狀態的監聽,使用 SCNetworkReachabilityUnscheduleFromRunLoop 方法取消之前在 Main Runloop 中的監聽

    2. (void)stopMonitoring { if (!self.networkReachability) {
        return;
      
      }

</pre>

SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}</li>
  • 創建一個在每次網絡狀態改變時的回調

    weak typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        strong typeof(weakSelf)strongSelf = weakSelf;

    </pre>

    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {
        strongSelf.networkReachabilityStatusBlock(status);
    }
    

    };

    • 每次回調被調用時
      • 重新設置 networkReachabilityStatus 屬性
      • 調用 networkReachabilityStatusBlock
      </li> </ul> </li>
    • 創建一個 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;

      SCNetworkReachabilityContext context = { 0, (bridge void )callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL }; </pre>

      • 其中的 callback 就是上一步中的創建的 block 對象
      • 這里的 AFNetworkReachabilityRetainCallback 和 AFNetworkReachabilityReleaseCallback 都是非常簡單的 block,在回調被調用時,只是使用 Block_copy 和 Block_release 這樣的宏
      • 傳入的 info 會以參數的形式在 AFNetworkReachabilityCallback 執行時傳入

        static const void AFNetworkReachabilityRetainCallback(const void info) { return Block_copy(info); }</p>

        static void AFNetworkReachabilityReleaseCallback(const void info) { if (info) { Block_release(info); } }</p> </li> </ul> </li>

      • 當目標的網絡狀態改變時,會調用傳入的回調

        SCNetworkReachabilitySetCallback(
            (bridge SCNetworkReachabilityRef)networkReachability,
            AFNetworkReachabilityCallback, 
            &context
        );
        </pre> </li> 
           
      • 在 Main Runloop 中對應的模式開始監控網絡狀態

        SCNetworkReachabilityScheduleWithRunLoop(
            (__bridge SCNetworkReachabilityRef)networkReachability, 
            CFRunLoopGetMain(), 
            kCFRunLoopCommonModes
        );
        
      • 獲取當前的網絡狀態,調用 callback

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
            SCNetworkReachabilityFlags flags;
            if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {
                AFPostReachabilityStatusChange(flags, callback);
            }
        });
        
      • </ol>

        在下一節中會介紹上面所提到的一些 C 函數以及各種回調。

        設置 networkReachabilityStatusBlock 以及回調

        在 Main Runloop 中對網絡狀態進行監控之后,在每次網絡狀態改變,就會調用 AFNetworkReachabilityCallback 函數:

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

        這里會從 info 中取出之前存在 context 中的 AFNetworkReachabilityStatusBlock 。

        weak typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        strong typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }
        
        

        }; </pre>

        取出這個 block 之后,傳入 AFPostReachabilityStatusChange 函數:

        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];
            });
        }
        
        1. 調用 AFNetworkReachabilityStatusForFlags 獲取當前的網絡可達性狀態
        2. 在主線程中異步執行 上面傳入的 callback block(設置 self 的網絡狀態,調用 networkReachabilityStatusBlock )
        3. 發送 AFNetworkingReachabilityDidChangeNotification 通知.
        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>

        因為 flags 是一個 SCNetworkReachabilityFlags ,它的不同位代表了不同的網絡可達性狀態,通過 flags 的位操作,獲取當前的狀態信息 AFNetworkReachabilityStatus 。

        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>

        這里就是在 SystemConfiguration 中定義的全部的網絡狀態的標志位。

        與 AFNetworking 協作

        其實這個類與 AFNetworking 整個框架并沒有太多的耦合。正相反,它在整個框架中作為一個 即插即用 的類使用,每一個 AFURLSessionManager 都會持有一個 AFNetworkReachabilityManager 的實例。

        self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];  
        

        這是整個框架中除了 AFNetworkReachabilityManager.h/m 文件, 唯一一個 引用到這個類的地方。

        在實際的使用中,我們也可以直接操作 AFURLSessionManager 的 reachabilityManager 來獲取當前的網絡可達性狀態,而不是自己手動初始化一個實例,當然這么做也是沒有任何問題的。

        總結

        1. AFNetworkReachabilityManager 實際上只是一個對底層 SystemConfiguration 庫中的 C 函數封裝的類,它為我們隱藏了 C 語言的實現,提供了統一的 Objective-C 語言接口
        2. 它是 AFNetworking 中一個即插即用的模塊

        來自: http://draveness.me/afnetworking4/

  •  本文由用戶 sy7118 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
     轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
     本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!