ネットワーク接続状況を知る

2010年8月13日金曜日 | Published in | 4 コメント

このエントリーをはてなブックマークに追加

2010-12-13 続きのブログを書きました。そちらも合わせて読んでみてください。
Cocoaの日々: ネットワーク接続状況を知る[2] SCNetworkReachabilityGetFlags はブロックする

ネットワーク接続状況を知るための API等を調べてみた。前半はアップル提供のサンプルコードについて、後半は自作サンプルについて(ソースコードは GitHubで公開)。

アップル提供のサンプルコード Reachability


SystemConfiguration フレームワークを使うとネットワークの接続状況(WiFi利用または3G回線利用か、接続不可か)を知ることができる。

iOS Reference Library に Reachability というサンプルがある。

Reachability: Classes/Reachability.m

WiFi接続時


3G使用時

フライトモード時
この程度の情報が取れることがわかる。

利用は、まず SCNetworkReachabilityRef を取得する。
SCNetworkReachabilityRef reachability =
 SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
用途に応じて何種類かの関数が用意されている。
SCNetworkReachabilityCreateWithAddress
SCNetworkReachabilityCreateWithAddressPair
SCNetworkReachabilityCreateWithName

次に取得した SCNetworkReachabilityRef を SCNetworkReachabilityGetFlags()へ渡して SCNetworkReachabilityFlags を得る。この値はビットで接続状況を表している。
enum {
   kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
   kSCNetworkReachabilityFlagsReachable = 1<<1,
   kSCNetworkReachabilityFlagsConnectionRequired = 1<<2,
   kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3,
   kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
   kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5,
   kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16,
   kSCNetworkReachabilityFlagsIsDirect = 1<<17,
   kSCNetworkReachabilityFlagsIsWWAN = 1<<18,
   kSCNetworkReachabilityFlagsConnectionAutomatic
    = kSCNetworkReachabilityFlagsConnectionOnTraffic
};
typedef    uint32_t    SCNetworkReachabilityFlags;
これらのビットをチェックすることで 3G経由、WiFi経由、非接続の判断ができる。


自作サンプル


Apple提供のサンプルは若干複雑なので、簡略化したサンプルを作ってみる。サンプル作成にあたっては下記が参考になった。 How to check for local Wi-Fi (not just cellular connection) using iPhone SDK? - Stack Overflow これを参考に簡単なサンプルを作ってみた。NetworkReachability というクラスを用意し、このクラスに接続状況のチェックをさせる。
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>

enum {
 kNetworkReachableNon = 0,
 kNetworkReachableWiFi,
 kNetworkReachableWWAN
};

#define NetworkReachabilityChangedNotification
 @"NetworkReachabilityChangedNotification"

@interface NetworkReachability : NSObject {

 SCNetworkReachabilityRef reachability_;
}

- (id)initWithHostname:(NSString*)hostname;
- (NSInteger)getConnectionMode;

- (BOOL) startNotifier;
- (void) stopNotifier;

@end
接続判定箇所。
- (int)getConnectionMode
{
 if (reachability_) {
  SCNetworkReachabilityFlags flags = 0;
  SCNetworkReachabilityGetFlags(reachability_, &flags);
  
  BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);
  BOOL needsConnection = ((flags & kSCNetworkFlagsConnectionRequired) != 0);
  if (isReachable && !needsConnection) {
   if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
    return kNetworkReachableWWAN;
   }
   
   if ([self getWiFiIPAddress]) {
    return kNetworkReachableWiFi;
   }
   
  }
 }
 return kNetworkReachableNon;
}
WiFi かどうかの判断箇所。
- (NSString*)getWiFiIPAddress
{
 BOOL success;
 struct ifaddrs * addrs;
 const struct ifaddrs * cursor;
 
 success = getifaddrs(&addrs) == 0;
 if (success) {
  cursor = addrs;
  while (cursor != NULL) {
   if (cursor->ifa_addr->sa_family == AF_INET
    && (cursor->ifa_flags & IFF_LOOPBACK) == 0) {
    NSString *name =
    [NSString stringWithUTF8String:cursor->ifa_name];
    
    if ([name isEqualToString:@"en0"]) { // found the WiFi adapter
     return [NSString stringWithUTF8String:
       inet_ntoa(((struct sockaddr_in *)cursor->ifa_addr)->sin_addr)];
       NSString* addressString = [NSString stringWithUTF8String:
       inet_ntoa(((struct sockaddr_in *)cursor->ifa_addr)->sin_addr)];
      freeifaddrs(addrs);
      return addressString;
    }
   }
   
   cursor = cursor->ifa_next;
  }
  freeifaddrs(addrs);
 }
 return NULL;
}
コードはほぼ下記から拝借した。 Matt Brown - Journal of a Software Engineer: How to get the IP address of an iPhone OS v2.2.1 シミュレータ上で実行した場合、WiFiに割り当てられているネットワークが必ずしも en0 ではない(大抵は en1)ので、接続なし表示になることがある。 接続状況に変化があった場合は Notificationをポストする。これはアップルのサンプルから拝借した。もともと用意されている SCNetworkReachabilitySetCallback() を使い Notificationポストへ変換している。
static void ReachabilityCallback(
 SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
 NSAutoreleasePool* myPool = [[NSAutoreleasePool alloc] init];
 NetworkReachability* noteObject = (NetworkReachability*)info;
 [[NSNotificationCenter defaultCenter]
  postNotificationName:NetworkReachabilityChangedNotification object:noteObject];
 
 [myPool release];
}

- (BOOL)startNotifier
{
 BOOL ret = NO;
 SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};
 if(SCNetworkReachabilitySetCallback(reachability_, ReachabilityCallback, &context))
 {
  if(SCNetworkReachabilityScheduleWithRunLoop(
             reachability_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
  {
   ret = YES;
  }
 }
 return ret;
} 

- (void) stopNotifier
{
 if(reachability_!= NULL)
 {
  SCNetworkReachabilityUnscheduleFromRunLoop(
   reachability_, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
 }
}
実行するとこんな感じ。


シミュレータを使って状態の変化を知るには実行後に Macの Wifiをオフにする。 Notificationがポストされて表示が切り替わる。実機の場合は、設定画面を開き WiFiを切ったり、フライトモードにしたりすると確認できる。ただしバックグラウンド実行に準拠していないので Notificationが受け取れるのはサンプルアプリを再び起動した時のみ。また検出までは数秒かかる。

- - - -
ソースコードは GitHub からどうぞ。
xcatsan's iOS-Sample-Code at 2010-08-13 - GitHub

2010-10-21 訂正

NetworkReachable/Classes at 2010-08-13b from xcatsan's iOS-Sample-Code - GitHub

2010-11-14 訂正 

NetworkReachable/Classes at 2010-08-13c from xcatsan's iOS-Sample-Code - GitHub

(差分)
xcatsan's iOS-Sample-Code at 2010-08-13c - GitHub

参考

SCNetworkReachability Reference
SCNetworkReachabilityのリファレンス

Responses

  1. Shinichi NOMURA says:
    2010年10月20日 16:58

    こんにちは。git のソースコードを DL させていただきました。ありがとうございます。

    NetworkReachability.m の dealloc の中で CFRetain となっている箇所は、CFRelease ではないでしょうか?

  2. Shinichi NOMURA says:
    2010年10月20日 16:58

    こんにちは。git のソースコードを DL させていただきました。ありがとうございます。

    NetworkReachability.m の dealloc の中で CFRetain となっている箇所は、CFRelease ではないでしょうか?

  3. xcatsan says:
    2010年10月21日 15:06

    Shinichi NOMURA さん、こんにちは。
    CFRetain => CFRelease の件は指摘の通りです。以前、別の方にも指摘されて直したつもりが直っていませんでした;;

    下記を使ってください。
    http://github.com/xcatsan/iOS-Sample-Code/tree/2010-08-13b/NetworkReachable/

    ブログも後ほど修正しておきます。
    指摘ありがとうございました。

  4. xcatsan says:
    2010年10月21日 15:06

    Shinichi NOMURA さん、こんにちは。
    CFRetain => CFRelease の件は指摘の通りです。以前、別の方にも指摘されて直したつもりが直っていませんでした;;

    下記を使ってください。
    http://github.com/xcatsan/iOS-Sample-Code/tree/2010-08-13b/NetworkReachable/

    ブログも後ほど修正しておきます。
    指摘ありがとうございました。

  5. Shinichi NOMURA says:
    2010年11月13日 23:04

    度々恐れ入ります…ソースを使わせていただいていて気づいたのですが、最新版 NetworkReachability.m の57行目でメモリリークがあるように思います。

    return の前に freeifaddrs(addrs);

    が必要ではないでしょうか?


    なお別件ではありますが、Core Data の記事等も大変参考になりました。この場を借りて御礼申し上げます(笑)

  6. Shinichi NOMURA says:
    2010年11月13日 23:04

    度々恐れ入ります…ソースを使わせていただいていて気づいたのですが、最新版 NetworkReachability.m の57行目でメモリリークがあるように思います。

    return の前に freeifaddrs(addrs);

    が必要ではないでしょうか?


    なお別件ではありますが、Core Data の記事等も大変参考になりました。この場を借りて御礼申し上げます(笑)

  7. xcatsan says:
    2010年11月14日 8:52

    NOMURAさん、おはようございます。
    freeifaddrs() は、指摘の通りのようです。

    探してみると NOMURAさん同様、指摘している記事が見つかりました。

    「[iPhone]Leakを使ってエリカ本のリークを発見」
    http://ameblo.jp/k-power2/archive1-201004.html

    ↑ ずばりメモリリークが発生していますね。

    とりあえず上記を参考に修正を入れておきました。

    今回も指摘ありがとうございます。
    とても助かりました。

  8. xcatsan says:
    2010年11月14日 8:52

    NOMURAさん、おはようございます。
    freeifaddrs() は、指摘の通りのようです。

    探してみると NOMURAさん同様、指摘している記事が見つかりました。

    「[iPhone]Leakを使ってエリカ本のリークを発見」
    http://ameblo.jp/k-power2/archive1-201004.html

    ↑ ずばりメモリリークが発生していますね。

    とりあえず上記を参考に修正を入れておきました。

    今回も指摘ありがとうございます。
    とても助かりました。

Leave a Response

人気の投稿(過去 30日間)