bha*_*tsb 3 variadic swift combine
Swift Combine 的声明式语法对我来说看起来很奇怪,而且似乎有很多事情是不可见的。
例如,以下代码示例在 Xcode Playground 中构建和运行:
[1, 2, 3]
.publisher
.map({ (val) in
return val * 3
})
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Something went wrong: \(error)")
case .finished:
print("Received Completion")
}
}, receiveValue: { value in
print("Received value \(value)")
})
Run Code Online (Sandbox Code Playgroud)
我看到我假设的是一个用 [1, 2, 3] 创建的数组文字实例。我猜它是一个数组文字,但我不习惯看到它“声明”而不将它分配给变量名或常量或使用 _=。
我在 .publisher 之后故意添加了一个新行。Xcode 是否忽略了空格和换行符?
由于这种风格,或者我对视觉解析这种风格的新鲜感,我错误地认为“,receiveValue:”是一个可变参数或一些新语法,但后来意识到它实际上是 .sink(...) 的参数。
首先,如果格式正确,阅读/理解这段代码会容易得多。让我们从这个开始:
[1, 2, 3]
.publisher
.map({ (val) in
return val * 3
})
.sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Something went wrong: \(error)")
case .finished:
print("Received Completion")
}
},
receiveValue: { value in
print("Received value \(value)")
}
)
Run Code Online (Sandbox Code Playgroud)
map表达式我们可以通过以下方式进一步清理地图:
使用隐式返回
map({ (val) in
return val * 3
})
Run Code Online (Sandbox Code Playgroud)使用隐式返回
map({ (val) in
val * 3
})
Run Code Online (Sandbox Code Playgroud)删除 param 声明周围不必要的括号
map({ val in
val * 3
})
Run Code Online (Sandbox Code Playgroud)删除不必要的换行符。有时它们对于视觉上的分离很有用,但这是一个足够简单的闭包,它只会增加不必要的噪音
map({ val in val * 3 })
Run Code Online (Sandbox Code Playgroud)使用隐式参数,而不是 a val,无论如何它都是非描述性的
map({ $0 * 3 })
Run Code Online (Sandbox Code Playgroud)使用尾随闭包语法
map { $0 * 3 }
Run Code Online (Sandbox Code Playgroud)带有编号的行,所以我可以很容易地参考。
/* 1 */[1, 2, 3]
/* 2 */ .publisher
/* 3 */ .map { $0 * 3 }
/* 4 */ .sink(
/* 5 */ receiveCompletion: { completion in
/* 6 */ switch completion {
/* 7 */ case .failure(let error):
/* 8 */ print("Something went wrong: \(error)")
/* 9 */ case .finished:
/* 10 */ print("Received Completion")
/* 11 */ }
/* 12 */ },
/* 13 */ receiveValue: { value in
/* 14 */ print("Received value \(value)")
/* 15 */ }
/* 16 */ )
Run Code Online (Sandbox Code Playgroud)
[1, 2, 3]第 1 行是一个数组字面量。它是一个表达式,就像1, "hi", true,someVariable或1 + 1。像这样的数组不需要分配给任何东西即可使用。
有趣的是,这并不一定意味着它是一个数组。相反,Swift 拥有ExpressibleByArrayLiteralProtocol. 任何符合类型都可以从数组文字初始化。例如,Set符合,所以你可以写:let s: Set = [1, 2, 3],你会得到一个Set包含1,2和3。在没有其他类型信息的情况下(例如Set上面的类型注释),Swift 使用Array作为首选的数组文字类型。
.publisher第 2 行调用publisher数组文字的属性。这将返回一个Sequence<Array<Int>, Never>. 这不是一个常规的Swift.Sequence,它是一个非通用协议,而是在模块的Publishers命名空间(无大小写的枚举)中找到Combine。所以它的完全限定类型是Combine.Publishers.Sequence<Array<Int>, Never>.
它是一个Publisherwho Outputis Int,其Failure类型是Never(即不可能出现错误,因为无法创建该Never类型的实例)。
map第 3 行调用上述值的map实例函数(又名方法)Combine.Publishers.Sequence<Array<Int>, Never>。每当一个元素通过这个链时,它就会被给定的闭包转换map。
1会进去,3会出来。2会进去,6会出来。3进去了,6出来了。到目前为止这个表达式的结果是另一个 Combine.Publishers.Sequence<Array<Int>, Never>
sink(receiveCompletion:receiveValue:)第 4 行是对 的调用Combine.Publishers.Sequence<Array<Int>, Never>.sink(receiveCompletion:receiveValue:)。有两个闭包参数。
{ completion in ... }封闭件被提供作为参数传递给参数标记receiveCompletion:{ value in ... }封闭件被提供作为参数传递给参数标记receiveValue:Sink 正在为Subscription<Array<Int>, Never>我们上面的值创建一个新的订阅者。当元素通过时,receiveValue闭包将被调用,并作为参数传递给它的value参数。
最终发布者将完成,调用receiveCompletion:闭包。到的参数completionPARAM将类型的值Subscribers.Completion,这与无论是枚举.failure(Failure)的情况下,或.finished情况。由于Failure类型是Never,因此实际上不可能在.failure(Never)这里创建值。所以完成将始终是.finished,这将导致print("Received Completion")被调用。该语句print("Something went wrong: \(error)")是死代码,永远无法到达。
没有任何单一的句法元素使此代码符合“声明性”的要求。声明式风格是与“命令式”风格的区别。在命令式风格中,您的程序由一系列命令或要完成的步骤组成,通常具有非常严格的顺序。
在声明式风格中,您的程序由一系列声明组成。实现这些声明所必需的细节被抽象出来,例如像Combine和这样的库SwiftUI。例如,在这种情况下,您声明了print("Received value \(value)")当数字从[1, 2, 3].publisher. 发布者是一个基本示例,但您可以想象发布者从文本字段中发出值,其中事件发生在未知时间。
我最喜欢的伪装命令式和声明式风格的例子是使用像Array.map(_:).
你可以写:
var input: [InputType] = ...
var result = [ResultType]()
for element in input {
let transformedElement = transform(element)
result.append(result)
}
Run Code Online (Sandbox Code Playgroud)
但是有很多问题:
for是这样一个通用的构造,所以这里有很多可能。要确切了解发生了什么,您需要查看更多细节。你错过了一个优化机会,因为没有调用Array.reserveCapacity(_:). 这些重复调用append可以达到result数组缓冲区的最大容量。在那时候:
result需要复制的现有元素transformedElement必须添加新的这些操作可能会变得昂贵。随着您添加越来越多的元素,您可能会多次耗尽容量,从而导致多次此类重新增长操作。通过 callined result.reserveCapacity(input.count),您可以告诉数组预先分配一个大小合适的缓冲区,这样就不需要进行重新增长的操作。
该result数组必须是可变的,即使您可能永远不需要在其构造后对其进行变异。
这段代码可以写成对 的调用map:
let result = input.map(transform)
Run Code Online (Sandbox Code Playgroud)
这有很多好处:
map是一个非常具体的工具,它只能做一件事。一看到map,您就知道input.count == result.count,并且结果是transform函数/闭包的输出数组。map调用reserveCapacity,并且永远不会忘记这样做。result可以是不可变的。调用map遵循更具声明性的编程风格。您无需摆弄数组大小、迭代、追加或其他任何细节。如果你有input.map { $0 * $0 },你是在说“我想要输入的元素平方”,最后。map 的实现将具有执行此操作所需的for循环、appends 等。虽然它以命令式风格实现,但该函数将其抽象化,并允许您在更高的抽象级别编写代码,您不必在其中处理不相关的事情,例如for循环。