我最近写了一篇文章,来介绍 iOS 在连接新的 Wi-Fi 网络时,如何在弹出一个 web view 以让用户登录或注册之前,检测 Captive Portals (强制网络门户)。如果你连接过诸如酒店、酒吧或咖啡店等地的公共 Wi-Fi 网络,对这个应该会比较熟悉。如果你不熟悉 iOS 中 Captive Portals 的工作方式,可以查看 Solving the Captive Portal Problem on iOS 这篇文章,以了解一些背景知识。

多年来,Apple 的 Reachability 示例程序一直被用作 App 中检测网络访问的基础代码。搜索 Cocoapods.org 将会看到一个很长的第三方库列表,这些库基本上都是基于 Reachability,并考虑了 ARC 的支持或 Swift 的兼容等问题。
在 WWDC 2018 上,Apple 介绍了 iOS 12 中的一个新的框架:Network.framework,该框架包含了一个 NWPathMonitor 类。这个类为我们提供了一种监视网络状态变化的方法,而无需包含第三方库或 Apple 示例代码。

使用

只需简单导入 Network 框架,便可以使用 NWPathMonitor 类,如下创建一个 NWPathMonitor 实例:

let monitor = NWPathMonitor()

如果你只对某个特定网络适配器的状态变更感兴趣,例如 Wi-Fi,则可以使用 init(requiredInterfaceType:) 初始化方法,并提供 NWInterface.InterfaceType 值作为参数,来实例化 NWPathMonitor 对象,以监听指定类型的网络适配器,例如:

let monitor = NWPathMonitor(requiredInterfaceType: .wifi)

您需要确保在某处保留对 NWPathMonitor 对象的引用(例如使用 strong 属性),否则 ARC 可能会释放 NWPathMonitor 对象,从而导致指定的回调无法被调用。

可监控的网络类型包括:

  • cellular
  • loopback
  • other (对于虚拟或未确定的网络类型)
  • wifi
  • wiredEthernet

要获取状态更改的通知,需要为 pathUpdateHandler 属性指定一个回调,该回调将在网络接口发生状态更改时调用。例如,你的手机网络从蜂窝网络切换到 Wi-Fi 网络。然后,每当发生状态更改时,将返回一个 NWPath 实例,可以使用该实例以确定后续的操作,如下代码:

monitor.pathUpdateHandler = { path in
  if path.status == .satisfied {
    print("Connected")
  }
}

使用无参初始化方法与使用指定网络适配器的初始化方法的不同点是:返回的 NWPathobject 对象的 status 属性是否是 satisfied。例如,你只想监听蜂窝网络,而你的手机连接的是 Wi-Fi 网络,则当 Wi-Fi 网络状态发生变化时,并不会调用回调方法,并且 path 的 status 也会保持 unsatisfied 状态,因为手机没有使用指定的网络连接。所以,如果你只想知道是否有网络连接,无论是 Wi-Fi 还是蜂窝,则最好使用无参数的初始化方法。

一个有趣的问题是,NWPath 在 iOS 12 中是作为 Network 框架的一部分,而实际上在 iOS 9 中就有它的身影,不过是在 NetworkExtension.framework,两者之间有一些细微差别。

可以查询返回的 NWPath 对象,以查看设备的网络适配器的状态信息。另一个更有趣的属性是 isExpensive,它标识网络接口返回的数据收费是否昂贵,如使用蜂窝数据。我们同样可以查询是否支持 DNS、IPv4 或 IPv6。我们可以调用 usesInterfaceType 方法,来查看哪个接口改变了状态并触发回调:

let isCellular: Bool = path.usesInterfaceType(.cellular)

使用 NWPathMonitor 有点类似于使用其他 iOS API,例如 CLLocationManager,我们需要调用 start 方法以便开始接收更新,然后在完成后调用对应的 stop 方法。NWPathMonitor 的 start 方法要求我们为对象提供一个队列来执行其工作:

let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)

当我们完成监听状态的变化时,我们只需在调用 cancel() 方法。请注意,目前在 NWPathMonitor 上调用 cancel 后,我们无法再次启动监听,而是需要实例化一个新的 NWPathMonitor 实例。
请注意,如果在调用 start() 之前访问 NWPathMonitor 的 currentPath 属性,将返回 nil。实际上,如果你打印返回到更新回调的 path,如下所示:

monitor.pathUpdateHandler = { path in
  print(path)
}

则会打印以下内容:

Optional(satisfied (Path is satisfied), interface: en0, scoped, ipv4, ipv6, dns)

这表明此处返回的 NWPaths 和 currentPath 属性是可选项,尽管 API 没有明确说明(我们可以推断返回的 NWPath 引用是桥接到 Swift 的 Objective-C 指针)。

Captive Portals

Captive Portal 是在公共 Wi-Fi 热点连接时显示的网页,通常用于在授权访问 Internet(或访问其他网络资源)之前强制登录、注册或支付。在之前的一篇博客中,我谈到了从 App 开发的的角度来看,Reachability 看起来好像没什么问题,但实际上由于有 Captive Portals,它并不能很好完成任务。这可能导致 App 无法正常工作甚至于崩溃 -- 因为 App 可能期望从 RESTful API 中获取一些 JSON 数据,却从 Captive Portals 获取到了一些 HTML。

我之前很好奇 NWPathMonitor 在检测网络连接方面是否比 Reachability 有所改进。NWPath.Status 枚举确实提供了三种情况 -- satisfied、 unsatisfied 和 requiresConnection。不幸的是,Network.framework 的开发者文档并未提供这些枚举值的使用说明,而如果我们查看 NetworkExtension.framework 文档,其中的 NWPathStatus 对象提供了 satisfiable 枚举值,里面有一些相关文档描述:

The path is not currently satisfied, but may become satisfied upon a connection attempt. This can be due to a service, such as a VPN or a cellular data connection not being activated.

requiresConnection 枚举值似乎类似于 NWPathStatus 对象的 satisfiable 值。
好消息是 NWPathMonitor 通常只在 captive portal 协商之后通知 path 被设置为 satisfiable 状态,即在弹出 web view 且用户登录后。而在没有弹出 captive portal 的情况下,将向用户显示一个 Action Sheet,提供了 Use Without Internet 和 Use Other Network 选项。如果用户选择了 Use Without Internet,则 NWPathMonitor 返回的 path 的状态是 satisfied,即便实际上并没有连网。

通过使用 Charles 做的一些实验,我发现除非选择 Use Without Internet,否则在初始化 Wi-Fi 网络连接的同时中断连接的情况下,NWPathMonitor 没有报告 NWPath 的 Status 被置为 statisfied。但是,如果网络连接已恢复,但随后被删除,则并不能检测到这种变更,并且 path 的状态未依然是 satisfied。如果用户仅在火车或酒店上支付一小时的互联网访问费用,这种情况是可能发生的。

Connectivity

Connectivity 是一个 MIT 许可的开源框架,其目的是复用 iOS 现有的检测 captive portal 的方法。它允许在 iOS 8 上使用 Reachability 准确检测真正的 Internet 连接,这意味着在无法使用 NWPathMonitor 时,我们可以使用这个方法。并且在 iOS 12 上,Connectivity 使用了 NWPathMonitor 来提供更高的准确度。

Connectivity 已经提供了对 NWPathMonitor 的支持,可用于 iOS 12 系统。如果 framework 属性设置为 network,则会使用 Network 框架来替代 SystemConfiguration 框架(Reachability),以监听网络适配器的状态变更。

let connectivity = Connectivity()
connectivity.framework = .network

在网络适配器中的状态更改后,Connectivity 会执行大量检查以确定 Internet 访问是否可用。另外还有一个轮询选项,可以用来轮询网络是否可用,即使状态并未发生改变。可以通过设置 isPollingEnabled = true 并将 pollingInterval 设置为适当的时间值来实现这一点。

总结

Network 框架引入了一些很棒的新类,包括 NWPathMonitor,可用于在 iOS 12 上监听设备网络适配器的状态变化。在用户与 captive portal 交互后会将 path 的状态设置为 satisfied,但不会检测后续网络访问的丢失。Connectivity 可以为支持之前 iOS 系统的 App 提供向后兼容性,并通过使用 NWPathMonitor 获取更高的准确性。

优点

  • Apple 官方支持;
  • 无需包含第三方代码 - 只需导入 iOS 12 中的 Network.framework 即可;
  • 在与 captive portal 协商后,报告 NWPath 的状态为 satisfied;

缺点

  • 不能在 iOS 12 之前使用,这意味着如果你需要支持早期版本的 iOS,就会稍显麻烦了;
  • 缺乏详细文档;
  • 在初始连接成功后,不会再检测 captive portals 以及其他 Internet 连接中断的情况;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持Devmax。

iOS 12+ 中检测网络访问的方法的更多相关文章

  1. PHP简单检测网址是否能够正常打开的方法

    这篇文章主要介绍了PHP简单检测网址是否能够正常打开的方法,涉及php中curl的简单使用技巧,需要的朋友可以参考下

  2. OpenCV形状检测的示例详解

    本文主要介绍了OpenCV中的形状检测,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. android多开器解析与检测实现方法示例

    最近有业务上的要求,要求app在本地进行诸如软件多开、hook框架、模拟器等安全检测,防止作弊行为,下面这篇文章主要给大家介绍了关于android多开器解析与检测实现的相关资料,需要的朋友可以参考下

  4. iOS 12中无法获取WiFi的SSID了?别慌看这里!

    这篇文章主要给大家介绍了关于iOS 12中无法获取WiFi的SSID的相关资料,文中通过示例代码以及图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. JQuery Ajax如何实现注册检测用户名

    这篇文章主要介绍了JQuery Ajax如何实现注册检测用户名,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

  6. Python数据分析基础之异常值检测和处理方式

    这篇文章主要介绍了Python数据分析基础之异常值检测和处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  7. Python OpenCV Hough直线检测算法的原理实现

    这篇文章主要介绍了Python OpenCV Hough直线检测算法的原理实现,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

  8. JavaScript中检测数据类型的四种方法

    这篇文章主要给大家分享的是JavaScript中检测数据类型的四种方法,有 typeof、instanceof、constructor、Object.prototype.toString.call(),下面文章详细介绍内容,需要的小伙伴可以参考一下

  9. 详解JavaScript中的数据类型,以及检测数据类型的方法

    这篇文章主要介绍了JavaScript中的数据类型,以及检测数据类型的方法,帮助大家巩固基础知识,感兴趣的朋友可以了解下

  10. IOS中判断卡顿的方案总结

    这篇文章主要介绍了IOS中判断卡顿的方案总结,对IOS性能感兴趣的同学,一定要看一下

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部