Swift Combine的声明式语法对我来说似乎很奇怪,并且似乎有很多事情是不可见的。
例如,以下代码示例在Xcode游乐场中构建并运行:
[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)")
})
我看到我假设是使用[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)")
}
)
清理
map
表达式我们可以通过以下方法进一步清理地图:
map({ (val) in
return val * 3
})
map({ (val) in
val * 3
})
map({ val in
val * 3
})
map({ val in val * 3 })
val
,该参数无论如何都是非描述性的map({ $0 * 3 })
map { $0 * 3 }
最后结果
带有编号的行,因此我可以轻松地返回。
/* 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 */ )
通过它。
第1行,
[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
作为首选数组文字类型。第2行,
.publisher
第2行正在调用数组文字的
publisher
属性。这将返回Sequence<Array<Int>, Never>
。那不是一个普通的 Swift.Sequence
,它不是一个通用的协议,而是在Publishers
模块的Combine
命名空间(无大小写的枚举)中找到的。因此,其完全限定类型为 Combine.Publishers.Sequence<Array<Int>, Never>
。这是一个
Publisher
,其Output
为Int
,并且Failure
类型为Never
(即,由于无法创建Never
类型的实例,因此不会出现错误)。第3行,
map
第3行正在调用上面
map
值的Combine.Publishers.Sequence<Array<Int>, Never>
实例函数(又称方法)。每当元素通过此链传递时,都会通过给map
的闭包进行转换。1
将进入,3
将出现。 2
进入,6
出来。 3
进入,6
出来。 到目前为止,此表达式的结果是另一个
Combine.Publishers.Sequence<Array<Int>, Never>
第4行,
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:
闭包。 completion
参数的参数将是 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)
}
但是有很多问题:
for
是这样的常规构造,因此在这里有很多可能。为了确切地了解会发生什么,您需要研究更多细节。 Array.reserveCapacity(_:)
,您错过了优化机会。这些对append
的重复调用可以达到result
数组缓冲区的最大容量。在那时候:result
的现有元素复制到transformedElement
这些操作可能会变得昂贵。并且,随着添加越来越多的元素,您可能会数次耗尽容量,从而导致许多此类重新生成操作。通过调用
result.reserveCapacity(input.count)
,您可以告诉数组预先分配一个大小合适的缓冲区,这样就不需要进行增长操作了。 result
数组也必须是可变的。 可以将此代码编写为对
map
的调用:let result = input.map(transform)
这有很多好处:
map
是一个非常特定的工具,只能做一件事。一看到map
,您就会知道input.count == result.count
,并且结果是transform
函数/ closure的输出数组。 map
调用reserveCapacity
,它永远不会忘记这样做。 result
可以是不可变的。 调用
map
遵循一种更具声明性的编程风格。您无需摆弄数组大小,迭代,附加等内容。如果您有input.map { $0 * $0 }
,那么您将说“我希望输入的元素平方”。 map的实现必须具有for
循环,append
等等。虽然它以命令式实现,但该函数将其抽象化,并允许您以更高的抽象级别编写代码,而不必在乎诸如for
循环之类的无关紧要的东西。
关于swift - 快速组合声明式语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59779962/