有没有办法使该方法返回可变值?

Is there any way to make the method return a mutable value?

提问人:hopy 提问时间:1/28/2022 最后编辑:hopy 更新时间:2/1/2022 访问量:815

问:

如下代码所示:

struct Person {
    var name: String
}

struct Group {
    var person: Person
    
    func callAsFunction() -> Person {
        // Person is immutable value
        person
    }
}

var james = Person(name: "James")
var group = Group(person: james)
group().name = "Wong" //ERROR: Cannot assign to property: function call returns immutable value

group() 返回一个不可变的值,该值无法更改!那么有没有办法让 callAsFunction() 方法返回一个可变值呢?

谢谢;)


更新:

我的想法是将组的所有呼叫和访问转移到组中的 Person 对象,就像直接使用 Person 一样。

我无法使用 dynamicMemberLookup,因为我不知道 Person 中会有什么方法或属性。例如,Person 中可能有 100 个方法和属性(而不是演示的只有一个 name 属性),我不可能用 dynamicMemberLookup 编写 100 个下标方法。

我的需求有点像 Ruby 语言中的代理对象。访问一个对象(组)实际上是访问其中的另一个对象(Person),就好像该组不存在一样。

Ruby 代理模式:https://refactoring.guru/design-patterns/proxy/ruby/example

CallAsFunction 是迄今为止最接近的实现,但要求 Person 不能是 Struct,否则无法将其分配给其属性。

也许还不能在 Swift 中实现此功能?

迅速 方法 不变性 可变

评论

1赞 flanker 1/28/2022
你想达到什么目的?您可以只访问 person 属性并将其分配给可变变量。或者,您可以将方法的输出分配给可变变量。 毫无意义 - 你不能调用 struct!。 会起作用的。group().namegroup.name = "Wong"
1赞 Rob 1/28/2022
@flanker “是没有意义的 - 你不能调用结构体!” - 它不是毫无意义的。根据 SE-0253,正是这样做的。他的问题是他正在处理值类型,因此返回一个结构体并尝试以这种方式更改其属性之一没有任何意义。他可以创建一个引用类型,或者干脆使用另一种模式。但是,本身在语法上是正确的,只是调用他的 .group().namecallAsFunctionPersongroup()callAsFunction
1赞 Rob 1/28/2022
@flanker - “会起作用的。” - 不,它不会。 不过会的。这违背了他的目的,他可能会探索和理解SE-0253SE-0216SE-0195。不确定这是否是一个很好的用例,但我欣赏 hoby 试图弄清楚这里发生了什么。group.name = "Wong"group.person.name = "Wong"
0赞 hopy 1/28/2022
请参阅我上面的更新;)
0赞 Quang Hà 1/28/2022
请改用引用类型

答:

2赞 matt 1/28/2022 #1

您使用了错误的动态方法。你想要的是.请密切关注。一、准备工作:dynamicMemberLookup

struct Person {
    var name: String
}

@dynamicMemberLookup
struct Group {
    var person: Person
    subscript(dynamicMember kp:WritableKeyPath<Person,String>) -> String {
        get { self.person[keyPath:kp] }
        set { self.person[keyPath:kp] = newValue }
    }
}

现在看看这让你说了什么:

var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")

你明白了吗?即使 Group 没有属性,我们也会设置 Group,结果是我们设置了 Group 的 The 确实有属性。namenamenamepersonname

评论

0赞 hopy 1/28/2022
请参阅我上面的更新;)
1赞 matt 1/28/2022
是的,但这并不能改变我的答案。dynamicMemberLookup 的初衷恰恰是 Swift 接近于类似 Ruby 的转发。
1赞 Rob 1/29/2022 #2

简单地返回(的副本),这是一个值类型。然后,您不能像那样改变它的属性。它等效于以下内容:callAsFunctionPerson

struct Person {
    var name: String
}

Person(name: "Foo").name = "Bar"

这将返回相同的错误:

enter image description here

如果是引用类型,则它本来可以工作,但不适用于值类型。即使你采用了你的值类型,并在改变它之前先将其分配给一个变量,你也只会改变你的副本,而不是原来的。Person

如果你想要你想要的行为,你可以按照 matt (+1) 的建议使用 SE-0195 中概述的 a。@dynamicMemberLookup


你说:

我不能使用,因为我不知道 .例如,可能有 100 个方法和属性(而不是演示的只有一个 name 属性),我不可能用 编写 100 个下标方法。dynamicMemberLookupPersonPersondynamicMemberLookup

您不需要“100 个下标方法”。这是背后的激励思想,即属性将动态确定。例如,这里有两个属性,但只有一个 .@dynamicMemberLookupPersonGroup@dynamicMemberLookup

struct Person {
    var name: String
    var city: String
}

@dynamicMemberLookup
struct Group {
    var person: Person
    subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
        get { person[keyPath: keyPath] }
        set { person[keyPath: keyPath] = newValue }
    }
}

var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")

如果要处理不同的类型,请将其设置为通用:

struct Person {
    var name: String
    var city: String
    var age: Int
}

@dynamicMemberLookup
struct Group {
    var person: Person
    subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
        get { person[keyPath: keyPath] }
        set { person[keyPath: keyPath] = newValue }
    }
}

var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)

评论

0赞 hopy 1/31/2022
谢谢!在您的示例中,Person 中的“name”和“city”都是 String 类型,但在我的情况下有 100 种不同的返回类型,所以我仍然需要编写 100 个下标方法;)
0赞 Rob 1/31/2022
不,只是让它通用。您只需要一个下标方法。
0赞 hopy 1/31/2022
是的,使用通用的属性!但是我如何在 Person from Group 中调用方法?例如:Person中有“工作”方法,如何做到这一点:group.working()
0赞 hopy 1/31/2022
我尝试了@dynamicCallable和callAsFunction,但只能调用这样的工作方法:group().working()
0赞 Rob 2/1/2022
我不知道。我知道 SE-0216 - Future Directions 会考虑在未来的某个版本中,所以也许它会在未来做你想要的。@dynamicMemberCallable