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 中的函数构建器使用相同的原理,但它比我现在想进入的要微妙和复杂一些。

所以,为了更直接地回答你的问题——如果你想把你的代码分解成更易于管理的部分,我会创建一个 classstruct 封装了一些逻辑,然后将顺序相关的调用分解为新 classstruct 上的函数。我不确定您为什么要按照问题中所示的方式进行作业,这实际上没有任何意义,尤其是 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(&copy)
    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) 重新实现不变性,但这似乎超出了范围:我们在这里只提到过已经初始化的实例和可变属性。