Swift 5、是否可以分解赋值
Swift 5, is it possible to factorize assignations
自从 SwiftUI 及其将方法分解到括号中的能力(感谢函数构建器),就像这样:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World!")
Text("by Purple Giraffe").color(.gray)
}
}
这里的函数生成器代码只是为了强调分解可以很方便这一事实。我不希望它能帮助我进行因式分配。
我想知道是否可以像这样将赋值(带其他东西)分解到括号中:
struct AnimationViewConfiguration {
var contentMode:Int = 0
var mainTitle:String = "test"
var subTitle:String = ""
var alternativeSubtitle:String = ""
var numberOfIteration:Int = 0
var frameRate = 40
var maximumSimultaneousParalax:Int = 5
var minimumSimultaneousParalax:Int = 2
}
class someViewController: UIViewController {
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
func animateBackground(_ useAlternativeBackground:Bool) {
// The normal bulky way
if useAlternativeBackground == false {
mainBackgroundAnimationViewConfig.contentMode = 3
mainBackgroundAnimationViewConfig.mainTitle = "Your super animation"
mainBackgroundAnimationViewConfig.subTitle = "A subtitle anyway"
mainBackgroundAnimationViewConfig.alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// mainBackgroundAnimationViewConfig.numberOfIteration = 4
mainBackgroundAnimationViewConfig.frameRate = 40
mainBackgroundAnimationViewConfig.maximumSimultaneousParalax = 19
mainBackgroundAnimationViewConfig.minimumSimultaneousParalax = 3
} else {
// The way I'd like it to be
mainBackgroundAnimationViewConfig with { // imaginary `with` keyword
contentMode = 0
mainTitle = "Your super animation"
subTitle = "A subtitle anyway"
alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// numberOfIteration = 4
frameRate = 40
maximumSimultaneousParalax = 19
minimumSimultaneousParalax = 3
}
}
}
}
重点是要避免重复 15 次长变量名,因为您知道您经常这样做已经有 2、3、4 个缩进(这使得更烦人 ).
对于建议将它放在特定函数中的人,我会说出于同样的原因,我们有时会使用匿名函数(即只使用一次...),如果不进行赋值仍然很方便制作更多样板。
感谢@matt 提到在其他语言中用于此目的的 with
关键字~
如果不存在,是否会在 swift5.1+ 中出现?
你不觉得它很方便吗?
我觉得你有点糊涂了。这里有几个概念在起作用,而函数构建器并没有像您想象的那样很好地发挥作用。
当代码放在两个大括号之间时,我们称它为代码块。这是帮助组织程序的好方法,因为我们可以将相关的时序逻辑块放在一起。代码块最常出现的地方是在函数中。比如我有一个计算用户工资的函数:
func calculateSalary() {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
然后我们可以像这样调用函数:
calculateSalary()
但是,我们也可以将此函数分配给一个变量,我们将其称为 闭包。 (函数和闭包实际上是一回事,函数只是我们可以轻松重用和调用的命名闭包)。
let calculateSalary: () -> Void = {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
如您所料,我们可以用完全相同的方式调用该函数。
这意味着我们可以将闭包作为参数传递给方法或初始化器,以便另一个函数或初始化器可以调用我们传入的函数。例如:
/**
* this is a stupid, pointless class (but it demonstrates
* closures-as-parameters nicely)
*/
class SalaryCalculator {
let closure: () -> Void
init(closure: () -> Void) {
self.closure = closure
}
}
let calculator = SalaryCalculator(closure: {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
})
// call the closure!
calculator.closure()
这也可以使用尾随闭包语法来编写,如果闭包是我们的初始化程序(或函数)的最后一个参数,我们可以删除参数标签:
let calculator = SalaryCalculator {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
// call the closure!
calculator.closure()
这就是您在代码似乎封装在大括号中时所看到的(factorize 不是用于描述此的术语)。它实际上是一个传递给基础类型初始化程序的闭包参数。
SwiftUI 中的函数构建器使用相同的原理,但它比我现在想进入的要微妙和复杂一些。
所以,为了更直接地回答你的问题——如果你想把你的代码分解成更易于管理的部分,我会创建一个 class
或struct
封装了一些逻辑,然后将顺序相关的调用分解为新 class
或 struct
上的函数。我不确定您为什么要按照问题中所示的方式进行作业,这实际上没有任何意义,尤其是 Swift 的初始化程序无论如何都提供了如此多的可定制性。
wouldn't you find it handy?
没有。怎么了
var test = Test(
a : 3,
c : 4,
s : "hey"
)
开始?这使 other 属性保持默认值。
或者,如果你稍后变异,
test.c = 4
test.a = 3
test.s = "hey"
或
(test.c, test.a, test.s) = (4, 3, "hey")
?我不明白另一层语法糖是多么可取。
有些语言使用 with
构造来完成您描述的那种事情(每次都在单个引用上分配属性而不是显式点符号),但我并不渴望 Swift .
编辑后编辑:如果它只是您反对的长名称,复制到一个短名称的临时变量,设置所需的属性,然后复制回来:
var thisIsAReallyLongName = Whatever()
do {
var temp = thisIsAReallyLongName
temp.c = 4
temp.a = 3
temp.s = "hey"
thisIsAReallyLongName = temp
}
最接近你想要的是
struct Test {
var a:Int = 0
var b:Int = 0
var s:String = ""
mutating func update(_ closure: (inout Int, inout Int, inout String)->Void)->Void {
closure(&a, &b, &s)
}
}
var test = Test()
test.update { (a, b, c) in
a = 10
b = 20
c = "Alfa"
}
或更好(支持部分更新,xcode 为您提供可用的内容)
struct Test {
var a:Int = 0
var b:Int = 0
var s:String = ""
mutating func update(_ closure: (inout Self)->Void)->Void {
closure(&self)
}
}
var test = Test()
test.update { (v) in
v.a = 20
v.s = "Alfa"
}
更新:谁知道未来?
Swift New Features
You can call values of types that declare func
callAsFunction methods like functions. The call syntax is shorthand
for applying func callAsFunction methods.
struct Adder {
var base: Int
func callAsFunction(_ x: Int) -> Int {
return x + base
}
}
var adder = Adder(base: 3)
adder(10) // returns 13, same as
adder.callAsFunction(10)
You must include func callAsFunction argument
labels at call sites. You can add multiple func callAsFunction methods
on a single type, and you can mark them as mutating. func
callAsFunction works with throws and rethrows, and with trailing
closures. (59014791)
我自己也遇到过同样的问题,这就是我发现你的问题的方式(抱歉,如果你从那时起继续前进)。
我最初以为 SwiftUI 会建议一个答案,但它似乎对作业所做的只是将方法链接在一起:Text.color(:)
来自您的示例(实际上它不起作用,所以我不得不将其更改为 Text.foregroundColor(:)
) 是一种 return 具有新颜色的新 Text 实例的方法,并且 color
甚至可能不是结构的 属性,因为它似乎由未记录的 modifiers
包含 SwiftUI.Text.Modifier.color(Optional(gray))
的数组 运行 在您的示例之后。
所以你的严格要求让我开始思考,最后我找到了一个相当简单的解决方法,包括向你的结构添加一个 (public) change(:)
方法:
mutating func change(_ closure: (inout AnimationViewConfiguration) -> Void) {
closure(&self)
}
这将允许以下语法,我认为这非常接近我们的要求:
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
mainBackgroundAnimationViewConfig.change {
[=11=].contentMode = 0
[=11=].mainTitle = "Your super animation"
[=11=].subTitle = "A subtitle anyway"
[=11=].alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// numberOfIteration = 4
[=11=].frameRate = 40
[=11=].maximumSimultaneousParalax = 19
[=11=].minimumSimultaneousParalax = 3
}
除非我遗漏了什么,否则这应该可行……当然,您可以在其中 运行 任意代码,如果需要,您需要在调用方站点明确引用 self
;并且可能有一些需要注意的保留周期警告,但乍一看,除了标准的闭包警告之外,我看不到任何东西。但请务必指出您可能发现的任何问题!
显然,此版本仅适用于可变属性:尝试更改 let
常量不会编译,无论结构本身是常量还是它的任何属性。对于前一种情况,您可以 return 修改后的副本:
func copyWithChanges(_ closure: (inout AnimationViewConfiguration) -> Void) -> AnimationViewConfiguration {
var copy = self
closure(©)
return copy
}
// call it like this:
let newVersionOfTheConfig = mainBackgroundAnimationViewConfig.copyWithChanges {
[=12=].contentMode = 12
// etc.
}
对于类,无论它们是变量还是常量,它都更简单(如果我们仍然假设它们的所有属性都是可变的),因为闭包将获得对它的实例的引用可以随意修改:
func change(_ closure: (AnimationViewConfiguration2) -> Void) {
closure(self)
}
编辑: 从 Swift 5.2 开始,您可以将 change(_:)
函数命名为 callAsFunction(_:)
并在调用站点上保存一些输入:
// inside the struct declaration:
mutating func callAsFunction(_ closure: (inout AnimationViewConfiguration) -> Void) {
closure(&self)
}
// ...
// using the struct:
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
mainBackgroundAnimationViewConfig {
[=14=].contentMode = 0
[=14=].mainTitle = "Your super animation"
[=14=].subTitle = "A subtitle anyway"
[=14=].alternativeSubtitle = "Hey another one!"
}
结束编辑
但是,如果您想使用此语法来初始化一个实例,或者为它的任何不可变属性制作一个具有新值的副本,老实说,我看不出有任何方法可以实现,即使我们尝试使用“延迟初始化”属性 包装器 (example use case in the proposal for the feature) 重新实现不变性,但这似乎超出了范围:我们在这里只提到过已经初始化的实例和可变属性。
自从 SwiftUI 及其将方法分解到括号中的能力(感谢函数构建器),就像这样:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World!")
Text("by Purple Giraffe").color(.gray)
}
}
这里的函数生成器代码只是为了强调分解可以很方便这一事实。我不希望它能帮助我进行因式分配。
我想知道是否可以像这样将赋值(带其他东西)分解到括号中:
struct AnimationViewConfiguration {
var contentMode:Int = 0
var mainTitle:String = "test"
var subTitle:String = ""
var alternativeSubtitle:String = ""
var numberOfIteration:Int = 0
var frameRate = 40
var maximumSimultaneousParalax:Int = 5
var minimumSimultaneousParalax:Int = 2
}
class someViewController: UIViewController {
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
func animateBackground(_ useAlternativeBackground:Bool) {
// The normal bulky way
if useAlternativeBackground == false {
mainBackgroundAnimationViewConfig.contentMode = 3
mainBackgroundAnimationViewConfig.mainTitle = "Your super animation"
mainBackgroundAnimationViewConfig.subTitle = "A subtitle anyway"
mainBackgroundAnimationViewConfig.alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// mainBackgroundAnimationViewConfig.numberOfIteration = 4
mainBackgroundAnimationViewConfig.frameRate = 40
mainBackgroundAnimationViewConfig.maximumSimultaneousParalax = 19
mainBackgroundAnimationViewConfig.minimumSimultaneousParalax = 3
} else {
// The way I'd like it to be
mainBackgroundAnimationViewConfig with { // imaginary `with` keyword
contentMode = 0
mainTitle = "Your super animation"
subTitle = "A subtitle anyway"
alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// numberOfIteration = 4
frameRate = 40
maximumSimultaneousParalax = 19
minimumSimultaneousParalax = 3
}
}
}
}
重点是要避免重复 15 次长变量名,因为您知道您经常这样做已经有 2、3、4 个缩进(这使得更烦人 ).
对于建议将它放在特定函数中的人,我会说出于同样的原因,我们有时会使用匿名函数(即只使用一次...),如果不进行赋值仍然很方便制作更多样板。
感谢@matt 提到在其他语言中用于此目的的 with
关键字~
如果不存在,是否会在 swift5.1+ 中出现?
你不觉得它很方便吗?
我觉得你有点糊涂了。这里有几个概念在起作用,而函数构建器并没有像您想象的那样很好地发挥作用。
当代码放在两个大括号之间时,我们称它为代码块。这是帮助组织程序的好方法,因为我们可以将相关的时序逻辑块放在一起。代码块最常出现的地方是在函数中。比如我有一个计算用户工资的函数:
func calculateSalary() {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
然后我们可以像这样调用函数:
calculateSalary()
但是,我们也可以将此函数分配给一个变量,我们将其称为 闭包。 (函数和闭包实际上是一回事,函数只是我们可以轻松重用和调用的命名闭包)。
let calculateSalary: () -> Void = {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
如您所料,我们可以用完全相同的方式调用该函数。
这意味着我们可以将闭包作为参数传递给方法或初始化器,以便另一个函数或初始化器可以调用我们传入的函数。例如:
/**
* this is a stupid, pointless class (but it demonstrates
* closures-as-parameters nicely)
*/
class SalaryCalculator {
let closure: () -> Void
init(closure: () -> Void) {
self.closure = closure
}
}
let calculator = SalaryCalculator(closure: {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
})
// call the closure!
calculator.closure()
这也可以使用尾随闭包语法来编写,如果闭包是我们的初始化程序(或函数)的最后一个参数,我们可以删除参数标签:
let calculator = SalaryCalculator {
let monthly = 3500
let yearlyTotal = monthly * 12
print("Your salary is \(yearlyTotal)")
}
// call the closure!
calculator.closure()
这就是您在代码似乎封装在大括号中时所看到的(factorize 不是用于描述此的术语)。它实际上是一个传递给基础类型初始化程序的闭包参数。
SwiftUI 中的函数构建器使用相同的原理,但它比我现在想进入的要微妙和复杂一些。
所以,为了更直接地回答你的问题——如果你想把你的代码分解成更易于管理的部分,我会创建一个 class
或struct
封装了一些逻辑,然后将顺序相关的调用分解为新 class
或 struct
上的函数。我不确定您为什么要按照问题中所示的方式进行作业,这实际上没有任何意义,尤其是 Swift 的初始化程序无论如何都提供了如此多的可定制性。
wouldn't you find it handy?
没有。怎么了
var test = Test(
a : 3,
c : 4,
s : "hey"
)
开始?这使 other 属性保持默认值。
或者,如果你稍后变异,
test.c = 4
test.a = 3
test.s = "hey"
或
(test.c, test.a, test.s) = (4, 3, "hey")
?我不明白另一层语法糖是多么可取。
有些语言使用 with
构造来完成您描述的那种事情(每次都在单个引用上分配属性而不是显式点符号),但我并不渴望 Swift .
编辑后编辑:如果它只是您反对的长名称,复制到一个短名称的临时变量,设置所需的属性,然后复制回来:
var thisIsAReallyLongName = Whatever()
do {
var temp = thisIsAReallyLongName
temp.c = 4
temp.a = 3
temp.s = "hey"
thisIsAReallyLongName = temp
}
最接近你想要的是
struct Test {
var a:Int = 0
var b:Int = 0
var s:String = ""
mutating func update(_ closure: (inout Int, inout Int, inout String)->Void)->Void {
closure(&a, &b, &s)
}
}
var test = Test()
test.update { (a, b, c) in
a = 10
b = 20
c = "Alfa"
}
或更好(支持部分更新,xcode 为您提供可用的内容)
struct Test {
var a:Int = 0
var b:Int = 0
var s:String = ""
mutating func update(_ closure: (inout Self)->Void)->Void {
closure(&self)
}
}
var test = Test()
test.update { (v) in
v.a = 20
v.s = "Alfa"
}
更新:谁知道未来?
Swift New Features
You can call values of types that declare func callAsFunction methods like functions. The call syntax is shorthand for applying func callAsFunction methods.
struct Adder {
var base: Int
func callAsFunction(_ x: Int) -> Int {
return x + base
}
}
var adder = Adder(base: 3)
adder(10) // returns 13, same as
adder.callAsFunction(10)
You must include func callAsFunction argument labels at call sites. You can add multiple func callAsFunction methods on a single type, and you can mark them as mutating. func callAsFunction works with throws and rethrows, and with trailing closures. (59014791)
我自己也遇到过同样的问题,这就是我发现你的问题的方式(抱歉,如果你从那时起继续前进)。
我最初以为 SwiftUI 会建议一个答案,但它似乎对作业所做的只是将方法链接在一起:Text.color(:)
来自您的示例(实际上它不起作用,所以我不得不将其更改为 Text.foregroundColor(:)
) 是一种 return 具有新颜色的新 Text 实例的方法,并且 color
甚至可能不是结构的 属性,因为它似乎由未记录的 modifiers
包含 SwiftUI.Text.Modifier.color(Optional(gray))
的数组 运行 在您的示例之后。
所以你的严格要求让我开始思考,最后我找到了一个相当简单的解决方法,包括向你的结构添加一个 (public) change(:)
方法:
mutating func change(_ closure: (inout AnimationViewConfiguration) -> Void) {
closure(&self)
}
这将允许以下语法,我认为这非常接近我们的要求:
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
mainBackgroundAnimationViewConfig.change {
[=11=].contentMode = 0
[=11=].mainTitle = "Your super animation"
[=11=].subTitle = "A subtitle anyway"
[=11=].alternativeSubtitle = "Hey another one!"
// partial or complete assignation
// numberOfIteration = 4
[=11=].frameRate = 40
[=11=].maximumSimultaneousParalax = 19
[=11=].minimumSimultaneousParalax = 3
}
除非我遗漏了什么,否则这应该可行……当然,您可以在其中 运行 任意代码,如果需要,您需要在调用方站点明确引用 self
;并且可能有一些需要注意的保留周期警告,但乍一看,除了标准的闭包警告之外,我看不到任何东西。但请务必指出您可能发现的任何问题!
显然,此版本仅适用于可变属性:尝试更改 let
常量不会编译,无论结构本身是常量还是它的任何属性。对于前一种情况,您可以 return 修改后的副本:
func copyWithChanges(_ closure: (inout AnimationViewConfiguration) -> Void) -> AnimationViewConfiguration {
var copy = self
closure(©)
return copy
}
// call it like this:
let newVersionOfTheConfig = mainBackgroundAnimationViewConfig.copyWithChanges {
[=12=].contentMode = 12
// etc.
}
对于类,无论它们是变量还是常量,它都更简单(如果我们仍然假设它们的所有属性都是可变的),因为闭包将获得对它的实例的引用可以随意修改:
func change(_ closure: (AnimationViewConfiguration2) -> Void) {
closure(self)
}
编辑: 从 Swift 5.2 开始,您可以将 change(_:)
函数命名为 callAsFunction(_:)
并在调用站点上保存一些输入:
// inside the struct declaration:
mutating func callAsFunction(_ closure: (inout AnimationViewConfiguration) -> Void) {
closure(&self)
}
// ...
// using the struct:
var mainBackgroundAnimationViewConfig = AnimationViewConfiguration()
mainBackgroundAnimationViewConfig {
[=14=].contentMode = 0
[=14=].mainTitle = "Your super animation"
[=14=].subTitle = "A subtitle anyway"
[=14=].alternativeSubtitle = "Hey another one!"
}
结束编辑
但是,如果您想使用此语法来初始化一个实例,或者为它的任何不可变属性制作一个具有新值的副本,老实说,我看不出有任何方法可以实现,即使我们尝试使用“延迟初始化”属性 包装器 (example use case in the proposal for the feature) 重新实现不变性,但这似乎超出了范围:我们在这里只提到过已经初始化的实例和可变属性。