gpt4 book ai didi

swift - 快速组合声明式语法

转载 作者:行者123 更新时间:2023-12-01 09:52:44 25 4
gpt4 key购买 nike

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"truesomeVariable1 + 1一样。像这样的数组不需要分配任何东西就可以使用。

    有趣的是,这不一定意味着它是一个数组。相反,Swift具有 ExpressibleByArrayLiteralProtocol。可以从数组文字中初始化任何符合条件的类型。例如, Set符合要求,因此您可以编写: let s: Set = [1, 2, 3],您将获得一个包含 Set123。在没有其他类型信息的情况下(例如,上面的 Set类型注释),S​​wift使用 Array作为首选数组文字类型。

    第2行, .publisher
    第2行正在调用数组文字的 publisher属性。这将返回 Sequence<Array<Int>, Never>。那不是一个普通的 Swift.Sequence ,它不是一个通用的协议,而是在 Publishers模块的 Combine命名空间(无大小写的枚举)中找到的。因此,其完全限定类型为 Combine.Publishers.Sequence<Array<Int>, Never>

    这是一个 Publisher,其 OutputInt,并且 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)")是死代码,永远无法到达。

    关于“声明式”的讨论

    没有任何语法元素可使此代码符合“声明性”的要求。声明式风格是与“命令式”风格的区别。在命令式中,您的程序由一系列命令或要完成的步骤组成,通常具有非常严格的顺序。

    以声明的方式,您的程序由一系列声明组成。实现这些声明所必需的细节都被抽象出来,例如 CombineSwiftUI之类的库。例如,在这种情况下,您声明每当从 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/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com