如何在iOS中判断“移动网络数据”是否启用或禁用(即使已连接WiFi)?

12

我有一个应用程序,想在一定的时间间隔后获取连接状态报告。 即使我已经连接或关联到Wifi网络,我也想知道是否启用了蜂窝网络上的数据访问。这意味着,如果经过检查,我可以优雅地从Wifi网络中断开连接,知道有可用的蜂窝连接,可以将设备连接到该连接。

当前的Reachability 方法只会在连接到蜂窝时提供有关蜂窝可用性的信息,并且在实际连接到接口之前没有太多关于获取此数据的信息。

寻找类似于Android中可用的解决方案,如此链接所述:link

澄清

我不是要查看我的设备是否具有蜂窝功能。我正在尝试确定用户是否已启用/禁用移动网络上的数据访问,并希望即使连接到Wifi,也能获得此信息。用户可以通过转到设置来打开或关闭此选项。


1
你好!你找到解决方案了吗?我现在也遇到了同样的问题。 - birdy
没有这样的API可以告诉您设备中蜂窝数据的可用性。您可以检查用户是否已为您的应用启用了蜂窝数据访问权限。但是仅此而已。除非设备连接到蜂窝网络,然后如果您尝试切换到其他接口(主要接口是蜂窝网络),则无法知道它。 - Shane D
7个回答

7
如果您的目标是iOS 12或更高版本,则苹果公司已经引入了作为“Network”框架一部分的“NWPathMonitor”类。您可以(如我所做的那样)实例化两个不同的监视器,一个用于蜂窝移动网络,另一个用于WiFi。
let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi)
let cellularMonitor = NWPathMonitor(requiredInterfaceType: .cellular)

假设你有一个类,用两个简单的布尔值跟踪两个连接状态,使用pathUpdateHandler属性,你可以这样做:

wifiMonitor.pathUpdateHandler = { path in
    self.isWifiConnected = path.status == .satisfied                
}
cellularMonitor.pathUpdateHandler = { path in 
    self.isCellularConnected = path.status == .satisfied
}

然后按照您的喜好处理自己。 记得调用start(_:)并提供串行队列来启动监视器(我提供了两个单独的队列); 有一个小问题,一旦取消监视器实例,您必须实例化一个新实例,因为它无法重新启动。


1
这应该是这里被接受的答案。这正好满足了楼主的要求。 - danialzahid94
我觉得这个还是有争议的,即使isWifiConnected返回true,也不意味着移动数据是关闭的。 - undefined

6

目前没有可用的API来查询移动数据是否已启用。您可以使用CTCellularData的cellularDataRestrictionDidUpdateNotifier和restrictedState来了解用户是否已为您的应用启用或禁用了移动数据访问。这是iOS允许的应用程序的最大限度。但即使如此,这也并不可靠,因为如果您从设备中取出SIM卡,它仍将向您提供先前受限状态。


是的,这是真的。我也检查了这个API,但它仍然不能给出准确的结果。如果之前的状态是无限制的,如果SIM卡被移除或蜂窝数据被禁用,它仍将给出无限制的结果。 - Shane D
cellularDataRestriction检查设置中的一个选项,该选项在设置应用程序中禁用了移动数据,仅适用于您的应用程序。 - undefined

0

我认为你需要重新考虑你正在计划的事情:你永远无法确定在未来的任何时间点是否可靠的(数据)连接将可用:

  • 用户可能已经通过其蜂窝提供商禁用了数据传输,因此如果WiFi断开连接,蜂窝连接仍然无法提供数据。
  • 或者用户可以立即在设置中更改这个设置。
  • 或者蜂窝连接可能在WiFi断开连接的同时断开连接(例如进入屏蔽房间等)
  • 还有许多其他事情可能发生,同时在一个秘密房间™中

你只需处理断开连接并在这种情况下提供一种仁慈的行为(而不是崩溃)。


同意你所说的。我寻找这个的原因是,如果当前的WiFi网络连接不好或RSSI信号弱,我想要切换到蜂窝数据。在这种情况下,我会查询是否有可用的蜂窝数据,如果有,就会断开与网络的连接。连接后,蜂窝数据传输是否发生或蜂窝连接是否中断由用户负责。实际上,这是一个要求,用户假定设备启用了蜂窝连接。 - Shane D
1
好吧,我很确定你不会被允许(由于苹果限制/缺失的API)强制设备断开WiFi连接并切换到蜂窝网络。尽管设备的设置中有这样一个选项,但是我不知道是否能够查询它是否已启用。 - Andreas Oetjen
API可以让你从已连接的网络中断开,并且避免连接到特定的WiFi网络,如果你愿意并且这些API是公共的。这就是我为什么要求这个其他部分的原因,以便我可以完成整个过程。假设我可以通过WiFi网络完成它,那么我是否也可以通过蜂窝接口类似地完成呢? - Shane D
我不知道有这样的API,其他人也不知道:https://dev59.com/cmLVa4cB1Zd3GeqP0uQ0 - 但如果你找到了这样的API,我会很高兴如果你能向我展示它们! - Andreas Oetjen
我并不是说我可以通过编程开关wifi。我想说的是有可用的API,可以让你断开与特定“wifi”网络的连接(也就是允许你取消关联网络),但设备将继续尝试与任何其他在范围内且先前连接过的网络关联,这就是如果你想保持连接到蜂窝网络,你可以避免连接到wifi网络的地方。如果您想执行上述操作,请参考此链接:https://developer.apple.com/reference/networkextension/nehotspothelper - Shane D

0

之前我曾经尝试着想解决这个问题,但很遗憾地告诉你,目前没有API可以快速而简单地解决这个问题,所以我决定采取一个变通的方法。我的思路是通过跟踪整个设备的移动数据使用情况,如果我在应用程序中存储了移动数据使用情况,那么我就可以知道用户设备上是否有活跃的移动数据。具体做法如下:首先编写扩展程序:

extension SystemDataUsage {
    public static var wwanCompelete: UInt64 {
        return SystemDataUsage.getDataUsage().wirelessWanDataSent + SystemDataUsage.getDataUsage().wirelessWanDataReceived
    }
}

然后是类:

class SystemDataUsage {

    private static let wwanInterfacePrefix = "pdp_ip"

    class func getDataUsage() -> DataUsageInfo {
        var ifaddr: UnsafeMutablePointer<ifaddrs>?
        var dataUsageInfo = DataUsageInfo()

        guard getifaddrs(&ifaddr) == 0 else { return dataUsageInfo }
        while let addr = ifaddr {
            guard let info = getDataUsageInfo(from: addr) else {
                ifaddr = addr.pointee.ifa_next
                continue
            }
            dataUsageInfo.updateInfoByAdding(info)
            ifaddr = addr.pointee.ifa_next
        }

        freeifaddrs(ifaddr)

        return dataUsageInfo
    }

    private class func getDataUsageInfo(from infoPointer: UnsafeMutablePointer<ifaddrs>) -> DataUsageInfo? {
        let pointer = infoPointer
        let name: String! = String(cString: pointer.pointee.ifa_name)
        let addr = pointer.pointee.ifa_addr.pointee
        guard addr.sa_family == UInt8(AF_LINK) else { return nil }

        return dataUsageInfo(from: pointer, name: name)
    }

    private class func dataUsageInfo(from pointer: UnsafeMutablePointer<ifaddrs>, name: String) -> DataUsageInfo {
        var networkData: UnsafeMutablePointer<if_data>?
        var dataUsageInfo = DataUsageInfo()

        if name.hasPrefix(wwanInterfacePrefix) {
            networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer<if_data>.self)
            if let data = networkData {
                dataUsageInfo.wirelessWanDataSent += UInt64(data.pointee.ifi_obytes)
                dataUsageInfo.wirelessWanDataReceived += UInt64(data.pointee.ifi_ibytes)
            }
        }
        return dataUsageInfo
    }
}

最后一个结构体:

struct DataUsageInfo {
    var wirelessWanDataReceived: UInt64 = 0
    var wirelessWanDataSent: UInt64 = 0

    mutating func updateInfoByAdding(_ info: DataUsageInfo) {
        wirelessWanDataSent += info.wirelessWanDataSent
        wirelessWanDataReceived += info.wirelessWanDataReceived
    }
}

我使用了这个答案的代码,你也可以检查一下: 使用Swift跟踪移动数据使用情况

0

我的解决方案可能有些过度,但可能会对某些人有用。但是,这个想法是考虑用户的蜂窝数据应用设置。它利用单例来避免重复初始化并保留BOOL以供依赖,并发布通知,可以在应用程序的任何地方观察到。

//  MobileDataPolicy.h
#ifndef MobileDataPolicy_h
#define MobileDataPolicy_h

#import <Foundation/Foundation.h>
@import CoreTelephony;

NS_ASSUME_NONNULL_BEGIN

extern NSNotificationName const kMobileDataPolicyNotification;
extern NSString * const kMobileDataPolicyAllowedKey;

@interface MobileDataPolicy : NSObject
+(BOOL)isAllowed;
@end

NS_ASSUME_NONNULL_END

#endif

并且

//  MobileDataPolicy.m
#import "MobileDataPolicy.h"

NSNotificationName const kMobileDataPolicyNotification = @"kMobileDataPolicyNotification";
NSString * const kMobileDataPolicyAllowedKey = @"kMobileDataPolicyAllowedKey";

@interface MobileDataPolicy ()
@property (nonatomic, readwrite) BOOL cellularDataAllowed;
@end

@implementation MobileDataPolicy

+(instancetype)singleton {
    static MobileDataPolicy * singletonInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if ( !singletonInstance ) {
            singletonInstance = [[MobileDataPolicy alloc] initReal];
            if (singletonInstance) {
                [singletonInstance setupCellularDataPolicyHandler];
            }
        }
    });
    return singletonInstance;
}

_Pragma("clang diagnostic push")
_Pragma("clang diagnostic ignored \"-Wobjc-designated-initializers\"")
-(instancetype)init {
    NSAssert(NO, @"init is not the designated initializer for instances of MobileDataPolicy. use [MobileDataPolicy isAllowed]");
    return nil;
}
_Pragma("clang diagnostic pop")

-(instancetype)initReal {
    if (!(self=[super init])) return nil;
    _cellularDataAllowed = NO;
    return self;
}

// ask for policy with [MobileDataPolicy allowed]
+(BOOL)isAllowed {
    //we need only one handler per App.
    return [MobileDataPolicy singleton].cellularDataAllowed;
}

#pragma mark setup - Cellular Data Policy Handler
- (void)setupCellularDataPolicyHandler {
    if (@available(iOS 9.0, *)) {
        CTCellularData *cellularData = [[CTCellularData alloc] init];
        
        //following handler block will run on default priority global dispatch queue
        [cellularData setCellularDataRestrictionDidUpdateNotifier:^(CTCellularDataRestrictedState state) {
            switch (state) {
                case kCTCellularDataRestrictedStateUnknown: self->_cellularDataAllowed = NO;
                    break;
                case kCTCellularDataRestricted: self->_cellularDataAllowed = NO;
                    break;
                case kCTCellularDataNotRestricted: self->_cellularDataAllowed = YES;
                    break;
                default:
                    break;
            }
            [[NSNotificationCenter defaultCenter] postNotificationName:kMobileDataPolicyNotification object:nil userInfo:@{kMobileDataPolicyAllowedKey:@(state)}];
        }];
    }
}
@end

例如使用..

 //class method that inits singleton and returns state
 BOOL reachCellularData = MobileDataPolicy.isAllowed;
 NSLog(@"initial cellular data allowed for app = %@",reachCellularData ? @"YES" : @"NO");
 
 //start observing
 id<NSObject> noteKeeper = [[NSNotificationCenter defaultCenter] addObserverForName:kMobileDataPolicyNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
     // do something on cellular Data Policy State change..
     int cellularDataState = [note.userInfo[kMobileDataPolicyAllowedKey] intValue];
 }];
 
 //stop observing, possibly in -(void)dealloc
 [[NSNotificationCenter defaultCenter] removeObserver:noteKeeper];

请注意,此报告是关于在设置中是否禁用了移动数据“对于您的应用程序”。它不受设置中全局禁用手机移动数据的大开关的影响。它也不受飞行模式以及用户拆卸SIM卡的影响。在没有硬件设备(如iPad、iPod)的设备上,此设置不会显示在用户界面中,因此它将始终处于启用状态。 - undefined

-1

https://github.com/ashleymills/Reachability.swift Reachability有一种方法可以确定网络是否可通过WWAN连接

var isReachableViaWWAN: Bool {
    // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
    return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
}

1
正如我所提到的,我不是在寻找当前是否连接到特定接口。我正在寻找一些东西,可以告诉我当我通过wifi连接时,蜂窝网络连接是否“可用”和“启用”,这样当我与wifi网络断开连接时,我就会知道设备将切换到蜂窝接口,以防操作系统无法关联到特定网络。 - Shane D

-2

你可以了解所有的情况:

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNetworkChange:) name:kReachabilityChangedNotification object:nil];
        Reachability *reachablity=[Reachability reachabilityWithHostName:@"google.com"];

        [reachablity startNotifier];

        //    reachablity = [Reachability reachabilityForInternetConnection];

        NetworkStatus remoteHostStatus = [reachablity currentReachabilityStatus];
        if(remoteHostStatus == NotReachable) {
            NSLog(@"network not available ");


        }
        else if (remoteHostStatus == ReachableViaWiFi) {
            NSLog(@"connect with wifi");


        }
        else if (remoteHostStatus == ReachableViaWWAN) {
            NSLog(@"Cellulor network ");


        }
        return netwrokCheck;

1
正如我所提到的,我不是在寻找当前是否连接到特定接口。我正在寻找一些东西,可以告诉我当我通过wifi连接时,蜂窝网络连接是否“可用”和“启用”,这样当我与wifi网络断开连接时,我就会知道设备将切换到蜂窝接口,以防操作系统无法关联到特定网络。 - Shane D
@Shane 你想知道你的应用程序在移动数据和Wi-Fi之间切换时是否会持续更新吗? - NAVEEN KUMAR
我不想知道切换后当前接口的状态。通知器或某种回调也可以工作。但我要找的是有关当前蜂窝状态的信息(可用性<设备中是否有sim卡(希望数据可用)>和已启用<在设置移动数据处打开>),即使我连接到WiFi网络时也是如此。我寻找这样做的原因是,如果当前WiFi网络连接不良,我会想切换到蜂窝。 - Shane D
CTTelephonyNetworkInfo *network_Info = [CTTelephonyNetworkInfo new]; CTCarrier *carrier = network_Info.subscriberCellularProvider;通过使用这个,你可以知道SIM卡是否可用,并且通过reachability class可以知道蜂窝数据类型和移动数据是否可用(启用/禁用)。如果你想切换--(从WiFi到蜂窝网络),你需要清除蜂窝数据,因为它比WiFi更强。你需要知道SIM卡是否启用3G、4G或2G,可以使用reachability类找到。如果你想要,我也可以转发。 - NAVEEN KUMAR
1
你的解决方案并没有解决问题。我猜你可能没有理解这个问题。你的答案只有在我完全切换到界面后才能起作用。你的答案API本身就说明了“currentReachabilityStatus”。它会给出当前状态,也就是说,如果你连接到WiFi,它会通过WiFi给你,类似地,对于蜂窝网络,它会给出wwan。 - Shane D
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接