Reachability and International Roaming

2019-05-27 02:07发布

问题:

I am using the Reachability class from Apple to determine whether or not data or wifi is available. However, a user (with a U.S. iPhone) is testing the app in another country and he is seeing the following error while trying to access the MapKit.

Console

In my research on this error, I have found that PBRequester is ProtocolBuffer.

2012-10-15 21:16:00.921 WrightsCS App[24239:907] PBRequester failed with Error Error Domain=kCFErrorDomainCFNetwork Code=-1018 "International roaming is currently off." UserInfo=0x1e5587d0 {NSErrorFailingURLKey=https://gsp13-cn.ls.apple.com/shift, NSErrorFailingURLStringKey=https://gsp13-cn.ls.apple.com/shift, NSLocalizedDescription=International roaming is currently off.}

Example

-(BOOL)hasDataConnection
{
    Reachability *networkReachability = [Reachability reachabilityForInternetConnection];
    NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];

    if ( networkStatus == NotReachable )
    {
        return NO;
    }
    else
    {
        if ( ! [Reachability reachabilityWithHostName:@"www.google.com"] )
            return NO;

        return YES;
    }
}

Question

How can we detect if the user is roaming or at least ensure that we do not encounter this error if by chance the user is roaming?

回答1:

OK - this is not an answer but may point you in the right direction. To start with, I can't find an Objective C class that seems to let you do this (e.g. NSURLRequest Class).

I think you will need to go directly to the CFNetwork Framework (CFNetwork Framework Reference) because looking at the CFNetworkError codes has these tantalising entries (which match your error message):

kCFURLErrorNotConnectedToInternet  = -1009,
kCFURLErrorInternationalRoamingOff = -1018,
kCFURLErrorDataNotAllowed          = -1020,

Unfortunately, I don't know the CFNetwork Framework and despite looking at the Apple docs I am no wiser about which CFNetwork Framework API's use these values. But I am convinced there will be a way.

My two "ideas" (and I use that word loosely):

  1. Use CFHost (and get a callback to query for the error code)
  2. UseCFNetDiagnosticCopyNetworkStatusPassively (and parse the returned string).

I would love to see the real answer if there's an expert out there.



回答2:

You can use third party services like http://ipinfo.io (my own service) to find out the current country of even carrier code based on the device's IP address. You can then compare that to the CTCarrier details to determine if the device is roaming. Here's the standard ipinfo.io API response:

$ curl ipinfo.io/24.32.148.1 
{
    "ip": "24.32.148.1",
    "hostname": "doc-24-32-148-1.pecos.tx.cebridge.net",
    "city": "Pecos",
    "region": "Texas",
    "country": "US",
    "loc": "31.3086,-103.5892",
    "org": "AS7018 AT&T Services, Inc.",
    "postal": "79772"
}

Custom packages are available that also include the mnc/mcc details of mobile IPs though. See http://ipinfo.io/developers for details.



回答3:

Apple actually recommends that you don't use Reachability if you can avoid it, but just make the call and check the result. Before the call, you can set allowsCellularAccess to NO to avoid expensive 3G connections.

Obviously you will get errors. In the special case where 3G was possible but not allowed, and there was no WiFi, you get kCFURLErrorDataNotAllowed. You might then ask the user if they want to allow you a 3G connection.

Otherwise you would get kCFURLErrorNotConnectedToInternet if there is no internet connection, and in the special case where there would be a 3G connection but data roaming is not allowed (like US phone in another country) you get kCFURLErrorInternationalRoamingOff. There's also kCFURLErrorCallIsActive which you would get on a phone that can't use 3G while on a call.



回答4:

I did that to check for roaming ( it's not a 100% full proof but..):

1.First when user loads the app for the first time ever save the carrier network into user defaults:

CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = [netinfo subscriberCellularProvider];
NSString *name = [[carrier carrierName] copy];
NSLog(@"Carrier Name: %@", name);

NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
self.homeNetwork = [standardUserDefaults objectForKey:SNAP_homeNetwork];
if(self.homeNetwork == nil || self.homeNetwork.length == 0){
    self.homeNetwork = [[Globals sharedInstance] getCarrier];
    [standardUserDefaults setObject:self.homeNetwork forKey:SNAP_homeNetwork];
    [standardUserDefaults synchronize];
}
  1. Everytime you need check the currentCarrier Vs the one you saved on the first launch of the app and if the names are different then the user is roaming.