2010-12-13 続きのブログを書きました。そちらも合わせて読んでみてください。
Cocoaの日々: ネットワーク接続状況を知る[2] SCNetworkReachabilityGetFlags はブロックする
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コードはほぼ下記から拝借した。 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ポストへ変換している。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; }
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 からどうぞ。
|
|2010-10-21 訂正
↓
|
|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のリファレンス
のむら says:
2010年10月20日 16:58
こんにちは。git のソースコードを DL させていただきました。ありがとうございます。
NetworkReachability.m の dealloc の中で CFRetain となっている箇所は、CFRelease ではないでしょうか?
のむら says:
2010年10月20日 16:58
こんにちは。git のソースコードを DL させていただきました。ありがとうございます。
NetworkReachability.m の dealloc の中で CFRetain となっている箇所は、CFRelease ではないでしょうか?
xcatsan says:
2010年10月21日 15:06
Shinichi NOMURA さん、こんにちは。
CFRetain => CFRelease の件は指摘の通りです。以前、別の方にも指摘されて直したつもりが直っていませんでした;;
下記を使ってください。
http://github.com/xcatsan/iOS-Sample-Code/tree/2010-08-13b/NetworkReachable/
ブログも後ほど修正しておきます。
指摘ありがとうございました。
xcatsan says:
2010年10月21日 15:06
Shinichi NOMURA さん、こんにちは。
CFRetain => CFRelease の件は指摘の通りです。以前、別の方にも指摘されて直したつもりが直っていませんでした;;
下記を使ってください。
http://github.com/xcatsan/iOS-Sample-Code/tree/2010-08-13b/NetworkReachable/
ブログも後ほど修正しておきます。
指摘ありがとうございました。
のむら says:
2010年11月13日 23:04
度々恐れ入ります…ソースを使わせていただいていて気づいたのですが、最新版 NetworkReachability.m の57行目でメモリリークがあるように思います。
return の前に freeifaddrs(addrs);
が必要ではないでしょうか?
なお別件ではありますが、Core Data の記事等も大変参考になりました。この場を借りて御礼申し上げます(笑)
のむら says:
2010年11月13日 23:04
度々恐れ入ります…ソースを使わせていただいていて気づいたのですが、最新版 NetworkReachability.m の57行目でメモリリークがあるように思います。
return の前に freeifaddrs(addrs);
が必要ではないでしょうか?
なお別件ではありますが、Core Data の記事等も大変参考になりました。この場を借りて御礼申し上げます(笑)
xcatsan says:
2010年11月14日 8:52
NOMURAさん、おはようございます。
freeifaddrs() は、指摘の通りのようです。
探してみると NOMURAさん同様、指摘している記事が見つかりました。
「[iPhone]Leakを使ってエリカ本のリークを発見」
http://ameblo.jp/k-power2/archive1-201004.html
↑ ずばりメモリリークが発生していますね。
とりあえず上記を参考に修正を入れておきました。
今回も指摘ありがとうございます。
とても助かりました。
xcatsan says:
2010年11月14日 8:52
NOMURAさん、おはようございます。
freeifaddrs() は、指摘の通りのようです。
探してみると NOMURAさん同様、指摘している記事が見つかりました。
「[iPhone]Leakを使ってエリカ本のリークを発見」
http://ameblo.jp/k-power2/archive1-201004.html
↑ ずばりメモリリークが発生していますね。
とりあえず上記を参考に修正を入れておきました。
今回も指摘ありがとうございます。
とても助かりました。