iOS 12+ 中检测网络访问的方法
我最近写了一篇文章,来介绍iOS在连接新的Wi-Fi网络时,如何在弹出一个webview以让用户登录或注册之前,检测CaptivePortals(强制网络门户)。如果你连接过诸如酒店、酒吧或咖啡店等地的公共Wi-Fi网络,对这个应该会比较熟悉。如果你不熟悉iOS中CaptivePortals的工作方式,可以查看SolvingtheCaptivePortalProblemoniOS这篇文章,以了解一些背景知识。
多年来,Apple的Reachability示例程序一直被用作App中检测网络访问的基础代码。搜索Cocoapods.org将会看到一个很长的第三方库列表,这些库基本上都是基于Reachability,并考虑了ARC的支持或Swift的兼容等问题。
在WWDC2018上,Apple介绍了iOS12中的一个新的框架:Network.framework,该框架包含了一个NWPathMonitor类。这个类为我们提供了一种监视网络状态变化的方法,而无需包含第三方库或Apple示例代码。
使用
只需简单导入Network框架,便可以使用NWPathMonitor类,如下创建一个NWPathMonitor实例:
letmonitor=NWPathMonitor()
如果你只对某个特定网络适配器的状态变更感兴趣,例如Wi-Fi,则可以使用init(requiredInterfaceType:)初始化方法,并提供NWInterface.InterfaceType值作为参数,来实例化NWPathMonitor对象,以监听指定类型的网络适配器,例如:
letmonitor=NWPathMonitor(requiredInterfaceType:.wifi)
您需要确保在某处保留对NWPathMonitor对象的引用(例如使用strong属性),否则ARC可能会释放NWPathMonitor对象,从而导致指定的回调无法被调用。
可监控的网络类型包括:
- cellular
- loopback
- other(对于虚拟或未确定的网络类型)
- wifi
- wiredEthernet
要获取状态更改的通知,需要为pathUpdateHandler属性指定一个回调,该回调将在网络接口发生状态更改时调用。例如,你的手机网络从蜂窝网络切换到Wi-Fi网络。然后,每当发生状态更改时,将返回一个NWPath实例,可以使用该实例以确定后续的操作,如下代码:
monitor.pathUpdateHandler={pathin ifpath.status==.satisfied{ print("Connected") } }
使用无参初始化方法与使用指定网络适配器的初始化方法的不同点是:返回的NWPathobject对象的status属性是否是satisfied。例如,你只想监听蜂窝网络,而你的手机连接的是Wi-Fi网络,则当Wi-Fi网络状态发生变化时,并不会调用回调方法,并且path的status也会保持unsatisfied状态,因为手机没有使用指定的网络连接。所以,如果你只想知道是否有网络连接,无论是Wi-Fi还是蜂窝,则最好使用无参数的初始化方法。
一个有趣的问题是,NWPath在iOS12中是作为Network框架的一部分,而实际上在iOS9中就有它的身影,不过是在NetworkExtension.framework,两者之间有一些细微差别。
可以查询返回的NWPath对象,以查看设备的网络适配器的状态信息。另一个更有趣的属性是isExpensive,它标识网络接口返回的数据收费是否昂贵,如使用蜂窝数据。我们同样可以查询是否支持DNS、IPv4或IPv6。我们可以调用usesInterfaceType方法,来查看哪个接口改变了状态并触发回调:
letisCellular:Bool=path.usesInterfaceType(.cellular)
使用NWPathMonitor有点类似于使用其他iOSAPI,例如CLLocationManager,我们需要调用start方法以便开始接收更新,然后在完成后调用对应的stop方法。NWPathMonitor的start方法要求我们为对象提供一个队列来执行其工作:
letqueue=DispatchQueue.global(qos:.background) monitor.start(queue:queue)
当我们完成监听状态的变化时,我们只需在调用cancel()方法。请注意,目前在NWPathMonitor上调用cancel后,我们无法再次启动监听,而是需要实例化一个新的NWPathMonitor实例。
请注意,如果在调用start()之前访问NWPathMonitor的currentPath属性,将返回nil。实际上,如果你打印返回到更新回调的path,如下所示:
monitor.pathUpdateHandler={pathin print(path) }
则会打印以下内容:
Optional(satisfied(Pathissatisfied),interface:en0,scoped,ipv4,ipv6,dns)
这表明此处返回的NWPaths和currentPath属性是可选项,尽管API没有明确说明(我们可以推断返回的NWPath引用是桥接到Swift的Objective-C指针)。
CaptivePortals
CaptivePortal是在公共Wi-Fi热点连接时显示的网页,通常用于在授权访问Internet(或访问其他网络资源)之前强制登录、注册或支付。在之前的一篇博客中,我谈到了从App开发的的角度来看,Reachability看起来好像没什么问题,但实际上由于有CaptivePortals,它并不能很好完成任务。这可能导致App无法正常工作甚至于崩溃--因为App可能期望从RESTfulAPI中获取一些JSON数据,却从CaptivePortals获取到了一些HTML。
我之前很好奇NWPathMonitor在检测网络连接方面是否比Reachability有所改进。NWPath.Status枚举确实提供了三种情况--satisfied、unsatisfied和requiresConnection。不幸的是,Network.framework的开发者文档并未提供这些枚举值的使用说明,而如果我们查看NetworkExtension.framework文档,其中的NWPathStatus对象提供了satisfiable枚举值,里面有一些相关文档描述:
Thepathisnotcurrentlysatisfied,butmaybecomesatisfieduponaconnectionattempt.Thiscanbeduetoaservice,suchasaVPNoracellulardataconnectionnotbeingactivated.
requiresConnection枚举值似乎类似于NWPathStatus对象的satisfiable值。
好消息是NWPathMonitor通常只在captiveportal协商之后通知path被设置为satisfiable状态,即在弹出webview且用户登录后。而在没有弹出captiveportal的情况下,将向用户显示一个ActionSheet,提供了UseWithoutInternet和UseOtherNetwork选项。如果用户选择了UseWithoutInternet,则NWPathMonitor返回的path的状态是satisfied,即便实际上并没有连网。
通过使用Charles做的一些实验,我发现除非选择UseWithoutInternet,否则在初始化Wi-Fi网络连接的同时中断连接的情况下,NWPathMonitor没有报告NWPath的Status被置为statisfied。但是,如果网络连接已恢复,但随后被删除,则并不能检测到这种变更,并且path的状态未依然是satisfied。如果用户仅在火车或酒店上支付一小时的互联网访问费用,这种情况是可能发生的。
Connectivity
Connectivity是一个MIT许可的开源框架,其目的是复用iOS现有的检测captiveportal的方法。它允许在iOS8+上使用Reachability准确检测真正的Internet连接,这意味着在无法使用NWPathMonitor时,我们可以使用这个方法。并且在iOS12上,Connectivity使用了NWPathMonitor来提供更高的准确度。
Connectivity已经提供了对NWPathMonitor的支持,可用于iOS12+系统。如果framework属性设置为network,则会使用Network框架来替代SystemConfiguration框架(Reachability),以监听网络适配器的状态变更。
letconnectivity=Connectivity() connectivity.framework=.network
在网络适配器中的状态更改后,Connectivity会执行大量检查以确定Internet访问是否可用。另外还有一个轮询选项,可以用来轮询网络是否可用,即使状态并未发生改变。可以通过设置isPollingEnabled=true并将pollingInterval设置为适当的时间值来实现这一点。
总结
Network框架引入了一些很棒的新类,包括NWPathMonitor,可用于在iOS12+上监听设备网络适配器的状态变化。在用户与captiveportal交互后会将path的状态设置为satisfied,但不会检测后续网络访问的丢失。Connectivity可以为支持之前iOS系统的App提供向后兼容性,并通过使用NWPathMonitor获取更高的准确性。
优点
- Apple官方支持;
- 无需包含第三方代码-只需导入iOS12中的Network.framework即可;
- 在与captiveportal协商后,报告NWPath的状态为satisfied;
缺点
- 不能在iOS12之前使用,这意味着如果你需要支持早期版本的iOS,就会稍显麻烦了;
- 缺乏详细文档;
- 在初始连接成功后,不会再检测captiveportals以及其他Internet连接中断的情况;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。