普通视图
iOS响应式编程Combine——简介
Combine
通过绑定事件处理(event-progressing)操作符来自定义处理异步事件
总览
Combine框架提供了一种声明式的Swift API, 用来随时处理各种值。这些值可以被当做各种各样的异步事件。Combine声明了发布者来公开那些随时可能变化的值。并且订阅者接收这些来自发布者的值。
-
Publisher
协议声明了一种可以随时传递一系列值的类型。发布者有operaters(操作符)根据从上游发布者收到的值采取行动并重新发布它们。 - 在发布者的链条终端,会有一个
Subscriber
根据它所接收到的元素来做出反应。发布者只在订阅者明确要求时才发布值。这使你的订阅者代码可以控制从与其连接的发布者那里接收事件的速度。
多种Foundation类型都是通过发布者来公开他们的功能,包括 Timer
,NotificationCenter
和 URLSession
。Combine
同时也为符合KVO(键值观察)的所有属性提供了内置发布者。
你可以绑定多个发布者的输出,并使其交互。例如,你可以订阅一个输入框(textField)的发布者来进行更新操作,用其文本值来执行一段网络请求。你后续也可以用其他发布者来执行请求的响应来更新APP。
采用了 Combine
之后,通过集中事件处理和排查问题的技术像嵌套闭包以及基于会话的回调来使你的代码将会变得更加易读和维护。
引子
接下来我们来看一个关于《利用Combine来接收和处理事件》的讨论。
概述
Combine
提供了一种声明式的途径来让你的APP处理各种事件。跟以前执行多个代理回调或者completionHandler闭包相比,亦可以为一个给定事件源创建一个执行链。链的每一部分都是由处理从上一步接收到的指定行为和元素的操作符组成的。
想象一下有一个App需要根据一个textField的text来适配一个tableView和collectionView,在APPKit中,每在textfield中键入一个字符,都会产生一个Notification
,你可以用Combine来对这个Notification
进行订阅。在收到通知后,你可以用操作符来改变事件传递的内容和计时,也可以用最后的结果来更新APP的用户界面。
连接Publisher和Subscriber
为了能用Combine来接收textfield的通知,访问NotificationCenter
实例并调用他的publisher(for: object:)
方法。这个调用方法携带通知名和资源对象,并且会返回一个产生通知元素的发布者。
let pub = NotificationCenter.default.publisher(for: NSControl.textDidChangeNotification, object: filterField)
用Subscriber
来接收来自发布者的元素。订阅者定义了一个关联类型,Input
, 来声明他所接收的类型。订阅者同样也会声明一个类型,Output
,来声明他产生的元素的类型。发布者和订阅者都会定义一种类型,Failure
,来代表他们产生或者接收的错误。想要将订阅者对接到发布者,Output
必须和Input
必须相匹配,同样Failure
类型也要相匹配.
Combine提供了两种内嵌的订阅者,这些订阅者会自动将属于他们的发布者的output和failure。
-
sink(receiveCompletion:receiveValue)
携带两个闭包。第一个闭包在接收到Subcribers.Completion
时执行,这个回调是一个代表着publisher正常结束或者发生错误。第二个闭包在他接收到来自publisher的元素时执行。 -
assign(to:on:)
会立即分发每个从一个既定对象的属性收到的元素,通过key-path来标示这个属性。
举个例子,你可以用sink
订阅者来在发布者完成时/每次收到元素时进行打印:
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.sink(receiveCompletion: { print ($0) }, receiveValue: { print ($0) })
sink(receiveCompletion:receiveValue)
和assign(to:on:)
两者都从他们的publisher那里请求了大量的元素,这些元素的数量是不受限的。想要控制接收元素的频率,可以通过实现Subscriber
协议来创建你自己的订阅者。
通过Operators来改变Output类型
前述的sink
订阅者都是在receive
闭包中执行他的业务。如果需要通过接收到的元素或者需要维持两者的调用来执行大量自定义业务,这一点就很烦人了。Combine的先进之处就在于他提供了大量的操作符来处理自定义事件传递。
比如, NotificationCenter.Publisher.Output,在接收比如来自textfield的字符串时,它并不是一个非常方便的类型。因为publisher的output本质上来讲是一个随时的元素序列, Combine提供了序列修改操作符,比如 map(_:)
, flatMap(maxPublishers:_:)
, 和reduce(_:_:)
。这些操作符与他们在Swift标准库中的对应体相似。
你可以添加一个map(_:)
操作符,这个操作符返回一个不同类型,来改变这个发布者的output。在这个案例中,你可以获取一个NSTextField
类型的通知对象,然后获取这个textField的stringValue
。
let sub = NotificationCenter.default .publisher(for: NSControl.textDidChangeNotification, object: filterField) .map( { ($0.object as! NSTextField).stringValue } ) .sink(receiveCompletion: { print ($0) }, receiveValue: { print ($0) })
在发布者链产生了你想要的类型之后,用assign(to:on:)
取代sink(receiveCompletion:receiveValue:)
。下面的例子懈怠了一个从发布者链接收到的字符串,并且将他们分发给一个自定义ViewModel对象的filterString:
let sub = NotificationCenter.default .publisher(for: NSControl.textDidChangeNotification, object: filterField) .map( { ($0.object as! NSTextField).stringValue } ) .assign(to: \MyViewModel.filterString, on: myViewModel)
自定义发布者和操作符
您可以使用一个运算符来扩展Publisher实例,该运算符执行否则需要手动编码的操作。以下是可以使用运算符来改进此事件处理链的三种方法:
-
您可以使用
filter(_:)
运算符来忽略特定长度的输入或拒绝非字母数字字符,而不是使用键入到文本字段中的任何字符串来更新视图模型。 -
如果过滤操作很耗时(例如,如果它查询一个大型数据库),您可能需要等待用户停止键入。为此,
debouce(for:scheduler:options:)
运算符允许您设置发布者发出事件之前必须经过的最短时间段。RunLoop
类提供了以秒或毫秒为单位指定时间延迟的便利。 -
如果结果更新了UI,则可以通过调用
receive(on:options:)
方法将回调传递给主线程。通过将RunLoop类提供的Scheduler实例指定为第一个参数,可以告诉Combine在主运行循环上调用订阅者。
声明如下:
let sub = NotificationCenter.default .publisher(for: NSControl.textDidChangeNotification, object: filterField) .map( { ($0.object as! NSTextField).stringValue } ) .filter( { $0.unicodeScalars.allSatisfy({CharacterSet.alphanumerics.contains($0)}) } ) .debounce(for: .milliseconds(500), scheduler: RunLoop.main) .receive(on: RunLoop.main) .assign(to:\MyViewModel.filterString, on: myViewModel)
在需要时取消发布
发布者持续发出元素,直到正常结束或失败。如果你不想再订阅发布者,一你一取消订阅。订阅者类型可以通过sink(receiveCompletion:receiveValue:)
和assign(to:on:)
来执行Cancellable
协议,这个协议提供了取消方法:
sub?.cancel()
如果你创建了一个自定义的Subscriber
,发布者会在你第一次订阅他的时候发送一个 Subscription
对象。存储这个订阅,并在要结束发布的时候调用cancel()
方法。当你创建了一个自定义的订阅者,你应该执行Cancellable
协议,并且让cancel()
方法来实现存储的订阅。
TCP/UDP介绍及区别
传输控制协议(TCP)驱动着可靠数据的传输。相较之下,用户数据包协议(UDP)优先于速度和效率,这一点对网络操作至关重要。
TCP和UDP协议是互联网的功能支柱,能将不同类型的的数据从一个网络资源传输给目标。TCP更可靠一点,同时,UDP优先于速度和效率。这篇文章将为您解释这两种协议的工作原理及两者之间的10大区别。
什么是TCP
TCP是一种面向连接的通讯协议,此协议允许计算设备和应用通过网络发送数据并核对其传递,是国际互联网重要支柱的一种形式。
TCP 依附于三次握手(同步/同步确认/最终确认)
通讯编程和计算设备利用TCP协议来交换网络之间的消息。这个协议的任务是携带一个穿梭于互联网之间的包,并保证互联网数据之间的消息/数据的成功传递。
在可以发送数据之前,客户端和服务端必须建立一个连接。一旦连接建立,服务端必须实时地动态监听客户端的请求。TCP协议是基于连接的,所以当数据在接收方和发送方之间传递的时候,他创建并维持两者之间的连接。因此,任何穿梭于网络的的信息都能保证准确无误的到达。
因为这一点,TCP是网络协议中最为广泛使用的一种协议。
下列几点是TCP最关键的几个特性:
- 客户端确认来自服务端的数据
- 超时后,服务端尝试重新传递没有被传递的数据
- 在拥挤的互联网中,TCP延迟数据传递
- TCP使用三次握手来查验数据传输错误
但是,TCP是一种本能可靠的协议,这些反馈机制也会导致更大的开销。这就意味着他会消耗大量的宽带来满足你的系统。大多数线上应用都使用的是UDP协议结合TCP来解决这个问题。
什么是UDP
UDP是一种面向消息的传输协议,这个协议允许计算设备和应用通过网络发送数据,但是并不需要对其传递进行核对,这完美契合于实时传输及广播系统
UDP能够连续不断地进行数据传输(即响应),而且并不需要确认连接。
跟TCP一样,UDP的目的也是收发消息,所以UDP的功能与TCP类似。UDP的特别之处在于:它不是基于连接的。在这种情况下,“无连接”指的是在通讯发生之前没有连接建立。
另外,它并不能保证来自服务端的数据包,这一点通常意义上可以被理解为“阅后即焚”协议。因为他并不关心客户端是否收到了数据。
大多数情况下,UDP要比TCP快,因为它不需要确认包的传递,但是TCP会确认。
UDP协议不适用于发送电子邮件/查看网页或者下载文件。但是它主要更倾向于事实应用,例如广播或者多任务网络传输。UDP的关键特征如下:
- 它适用于容忍数据包丢失的带宽密集型应用程序。
- 更少的数据传输延迟
- 一次可以发送大量的包
- 有可能会丢失数据
两者的10大区别
TCP在以下几点上异于UDP
TCP和UDP在组织上的关键区别
1. TCP是面向连接的,但是UDP是无连接的
因为TCP是面向连接的协议,他依赖于处于被动开放状态的服务器。一个被动开放的服务器监听每一个尝试连接他的客户端。客户端必须先和服务器连接,然后才能发送和接收数据。连接通过三次握手建立。客户端发送一个同步请求,服务端回发一个确认,然后客户端返回一个同步确认作为响应。 相较而言,UDP是一种无连接协议。这种数据传输需要一个网络终端来发送一个IT信号,并且不需要核验接收者是否可用或者能够收到这个信号。消息一旦发出,就不需要过多考虑接受者,也不需要考虑目的地。无连接传输协议可能会少量丢包。但是,这对于接收客户端来说并不总是那么明显,例如,在视频通话期间。
2. TCP 利用比 UDP 更多的错误检查机制
TCP使用三种不同机制来核验错误,并保证数据在传输时的完整性。这就使得它高度可靠,TCP通过以下方式进行错误核验:
- 超时后阻止连接: 连接有一个指定的超时时间。如果服务端或者客户端没有在这个时间段内收到确认消息,连接将被关闭,并且必须在你可以传输数据之前重新建立链接。
- 包含一个头部检查和区域: 数据包头部包含一个16比特的值,也就是我们所说的检查和区域。每个数据节点,TCP都会包含一个检查和区域,用来在传输过程中评估完整性
- 发送和接收确认: 当一个连接建立了以后,或者数据发送之后,服务器就会散射出一个确认或者一个ACK消息。客户端收到这个确认并且通过添加一个ACK消息值来回发他的消息。
这三点保证了正确的数据流在通过TCP传递的时候的不会出现任何丢失和出错。差异点在于,UDP只基于通过使用校验和校验错误来执行。
- TCP在特定序列中发送数据,而 UDP 协议没有固定的顺序:
为了确定需要将数据段交给哪个应用程序进程,TCP 使用端口号。 此外,它通过使用序列号与远程主机同步。 发送和接收的每个数据段都带有序列号。 这允许系统跟踪数据传输的特定顺序,从而保持所需的顺序。
UDP 不遵循排序机制。 数据包以无固定顺序独立发送,并在接收方应用程序中缝合在一起。 请记住,它们将按照接收顺序重新拼接在一起——也就是说,协议无法判断哪些数据包应该先到,以及它们是否以错误的顺序接收。 应用程序将错误地接收数据包。 UDP 还会丢弃任何它无法处理的数据包。
- UDP要比TCP更快更高效
尽管 UDP 存在内在缺陷,但 UDP 如此受欢迎的关键原因之一是它的速度和效率。 用户数据报协议不需要建立连接就可以开始发送数据包。 因此,它节省了打开服务器并将其置于“被动打开”侦听状态通常所需的时间。 它允许数据传输更快地开始,而不会延迟或延长延迟时间。 也不需要按顺序放置数据包或发送和接收确认,从而节省了时间。
除了延迟之外,UDP 在带宽方面也更高效。 一旦数据从服务器传输到客户端,TCP 就会参与许多错误检查机制、确认过程和排序措施,这些都会占用大量带宽。 相比之下,UDP 可以快速地将数据流从一个计算位置获取到另一个计算位置,而无需进行大量检查和平衡。 这使得它适用于低性能网络、移动设备和其他可能无法轻易获得资源的连接条件。
传输控制协议比 UDP 更慢并且更耗费资源。 如果数据序列被破坏,TCP 将重新启动连接,要求服务器发送和接收确认,建立三向握手等。UDP 简单地丢弃丢失或损坏的数据包,然后继续下一个 一,使其效率显着提高。
- 与 UDP 不同的是,TCP 不能用于多播或广播服务
TCP 是真正的端到端连接。 这意味着在一个通信端点和另一个端点之间建立连接,并采取准确的记录保存措施来跟踪发送的数据包和字节。 同步和确认消息将前一条消息的值加 1,使它们易于跟踪和追踪。 数据包标头还包含排序段,以保持数据流有序。 这些使其成为点对点传输系统的理想选择,而不是将数据广播到多个端点的场景。
在多播或广播场景中,服务器以多个接收者为目标。 它在不等待确认或任何交付确认的情况下中继数据,这正是 UDP 的工作方式。 UDP 的核心架构使其非常适合向整组端点(或子网)广播数据包,无论它们处于“被动打开”还是“侦听”状态。 在这种情况下,数据传输不会将特定的网络主机指定为目的地,而是将一组主机作为目标。
- TCP利用流量控制,但是UDP却没有
流量控制是一种机制,服务器通过这种机制首先检查接收方的容量,以了解它可以接受多少数据以及以什么速度接受。 传输控制协议通过滑动窗口的方式实现流量控制。 接收方允许发送方发送数据,直到滑动窗口中的窗口已满。 一旦发生这种情况,发送方必须等到接收方澄清有更大的窗口可用。
TCP 利用流量控制信息来校准数据传输的速度。 根据接收方主机,传输控制协议可以调整数据包传输的速度,避免接收方不堪重负。 然而,这也意味着服务器将在发送每个数据包之前等待流量控制信息,从而使其速度变慢且效率降低。
UDP 不使用任何流量控制技术。 它以最适合原始服务器的速度发送数据,因此,强大的服务器可能会用多个连续的数据流轰炸接收设备。 组织可以部署路由器来干预 UDP 数据流,并通过流量监管策略校准数据包发送的速度。 当UDP发送数据速度过快,接收方不堪重负时,它会直接丢弃接收方无法接受的数据包。
- UDP 不控制拥塞,而 TCP 实现拥塞避免算法
在流量控制中,TCP 根据接收方的接受窗口大小调整数据传输。 在这里,TCP 考虑了网络基础设施的容量。 除了接收者之外,网络还决定了数据传输的快慢程度。 因此,将传输速度校准到网络可接受的水平至关重要。 TCP 通过拥塞避免算法和策略实现这一点。
加性增加/乘性减少 (AIMD) 是使用的关键算法之一。 它结合了拥塞窗口的线性增长和指数减少来防止网络流量积累一个高度复杂但有效的过程。 TCP会等待拥塞的网络通道畅通后再恢复传输,保证数据包不丢失。
事实上,拥塞控制是 TCP 消耗如此多计算资源的主要原因。 传输控制协议可能使用 10 种以上的拥塞避免机制,具体取决于网络配置。 这可以包括 TCP Tahoe(在发生数据丢失时重新建立慢启动连接)、TCP Reno(为拥塞后恢复启动快速重传)和其他几个。
相比之下,UDP 没有办法控制网络拥塞。 如果路径上的流量过多,UDP 将丢弃排队等候的数据包并发送剩余的数据包。 组织可以使用专门配置的路由器来保存丢失的数据包,但这种功能并不是用户数据报协议所固有的。
- TCP要比UDP更可靠
两种协议各有利弊,TCP最大的优势在于可靠性高。 这可以归因于:
- 传输控制协议是基于连接的。 它只会将数据发送给正在侦听它的客户端。
- 它使用三向握手系统在数据传输一致的同时保持连接。 如果连接中断,传输也会停止,不会有数据包丢失。
- TCP 使用排序机制以正确的顺序发送数据。 这意味着通过此协议发送的图像、网页、数据文件和其他信息类型将以未损坏的状态到达。
- TCP 提供数据将被传送的保证。 它对收到的每个数据包都获得一个确认,只有在客户端发送 ACK 消息后才发送下一个数据包。
- TCP 使用流量和拥塞控制机制来确保数据不会丢失、损坏、复制或乱序传送。
相反,用户数据报协议本身并不可靠。 其架构旨在连续向一个或多个接收客户端发送数据包,而无需等待“侦听”状态或确认。 在具有挑战性的网络条件下,TCP 和 UDP 可能会导致数据包丢失。 不同的是,TCP 会识别丢失并识别丢失的数据包以重新传输信息。 UDP 无法判断数据包是否在传输过程中丢失,丢失了哪些数据包,或者如何重新发送它们。 这使得 UDP 不太可靠,尽管它更有效。
使用UDP协议的应用程序必须单独配置可靠性机制。 例如,可以单独配置数据传输的超时时间,如果在规定的时间内没有收到接收方的信号,则主动切断UDP协议。
- TCP标头与UDP标头不同
任何通信协议都允许以字节串形式交换信息。 这些“位串”包含多个字段,每个字段包含一些与特定协议相关的信息。 位串有两部分:标头和有效载荷。 有效负载包含消息的主体,而标头用于标识和支持通信协议的操作。 TCP 和 UDP 数据传输利用两种不同的标头。
首先,TCP 使用可变长度的标头来支持更复杂的数据传输,而不会影响可靠性。 标头可以有 20 到 60 个字节之间的任何位置。 相比之下,UDP 有一个固定长度的头部,速度快,效率高,但通用性差。 UDP 标头只能有八个字节。
TCP 和 UDP 标头(即它们的字段)也不同。 TCP 标头包含序列号、校验和、ACK 号、控制位、滑动窗口信息、源端口、目标端口等的指定字段。 相比之下,UDP 标头更短更简单,因为它们仅包含校验和、源端口、目标端口和一些其他元素的字段。
- UDP适用于现场实时数据传输,TCP不支持
尽管其本质上不可靠,但 UDP 仍然是在线操作的主要方式。 这是因为它非常适合实时数据传输,丢失几个数据包无关紧要。
例如,在网络游戏中,丢失的数据包只会跳过几帧,并可能导致玩家失去几分。 用户数据报协议会继续发送后续的数据包,用户可以继续播放。 但是,如果丢失了单个数据包,TCP 会注意到这一点。 它将重新启动连接并重新传输数据,这将冻结游戏。 在这种情况下,传输控制协议会对用户体验产生负面影响。
TCP 最适合数据完整性比传输速度更重要的用例。 它将确保文件和网页完好无损地到达,甚至有助于实时分析和内容交付网络,在这些网络中,丢失的数据包会篡改结果。 相比之下,UDP更适合媒体传输,比如:
- 视频通话:UDP 可以支持每秒 30 帧或更高刷新率的视频。 数据传输速度非常快,丢几个包也不会影响用户体验。
- 在线游戏:TCP 的许多清单和平衡将显著影响游戏体验。 没有完美的网络条件,帧会经常冻结,如果使用 TCP,连接会重新启动。 这就是推荐 UDP 的原因。
附加
传输控制协议和用户数据报协议以它们自己的方式提供帮助。 前者可靠,后者高效。 大多数网络和连接解决方案利用 TCP 和 UDP 来提供功能齐全的互联网体验。 通过了解 TCP 和 UDP 之间的主要区别,组织可以根据手头的用例正确配置网络并为最佳连接铺平道路。