robnik
1
I'm confused by this error. Is there a way around it? The ObservedObject here is ObservableObject which extends AnyObject. So I don't see why setting one of its properties should cause struct mutation.
Cannot assign to property: 'self' is immutable
Mark method 'mutating' to make 'self' mutable
The suggestion does not help, since that transfers the problem to var body, which can't be marked as mutating.
import SwiftUI
protocol ExerciseWithNoteRange {
associatedtype Restriction: Identifiable & Equatable
var noteRange: ClosedRange<Int> { get set }
var restriction: Restriction { get set }
}
struct ExampleGenericView<EX: ExerciseWithNoteRange & ObservableObject>: View {
@ObservedObject var exercise: EX
let restrictionsInOrder: [EX.Restriction]
let toString: (EX.Restriction) -> String
var body: some View {
List(restrictionsInOrder) { r in
listItem(r)
}
}
private func listItem(_ r: EX.Restriction) -> some View {
Text(verbatim: toString(r))
.onTapGesture {
exercise.restriction = r // ERROR HERE
}
}
}
1 Like
robnik
2
Well, it works if I add AnyObject to my protocol. That seems like a bug, since it's already part of ObservableObject.
protocol ExerciseWithNoteRange: AnyObject { ...
Karl
(👑🦆)
3
Hello,
This forum is only about the Swift language. Apple has requested that questions/comments about their proprietary frameworks (such as SwiftUI) should be asked on the Apple developer forums .
Thank you.
(And sorry - I don't like to do this)
robnik
4
Okay. I've tried to not ask questions here like "how do I layout a button", etc. But this one seems like it might be about the language, and SwiftUI is just an example that brings up the situation with structs, generics, and mutation. I don't know.
Sadly, in my experience the Apple developer forums are mostly a place to hear your voice echo into the void.
7 Likes
Karl
(👑🦆)
5
Understanding how structs and mutation applies in regular Swift does not automatically mean an understanding of how they apply in SwiftUI. SwiftUI has its own ideas about these concepts, and yes, it can be difficult to follow.
The example you give makes heavy use of SwiftUI-specific types, making it difficult to determine if this is a language question or not. If you can reduce it to a non-SwiftUI example, that would help.
I understand that many are unhappy with the Apple developer forums. You can also try StackOverflow or another site - but this site in particular has been asked not to provide support for SDK frameworks, and to instead direct developers to the ADF.
tera
6
This question is actually on topic. Minimal example that shows the same issue with no SwiftUI specifics:
protocol OtherProto: AnyObject {
}
protocol MyProto { // add ": AnyObject" to fix
associatedtype Restriction: Identifiable & Equatable
var restriction: Restriction { get set }
}
struct ExampleGenericView<T: MyProto & OtherProto> {
var exercise: T
func foo(_ r: T.Restriction) {
exercise.restriction = r // ERROR HERE
}
}
9 Likes
robnik
7
Ah, you beat me to it! I was just about to post that in response to Karl's suggestion. And we can make it a bit simpler still:
protocol Proto1 {
var someString: String { get set }
}
protocol ObjectProto: AnyObject {}
struct ExampleGenericStruct<T: ObjectProto & Proto1> {
var object: T
func foo(s: String) {
object.someString = s // Error here: Cannot assign to property: 'self' is immutable
}
}
So the question is: should the compiler be able to deduce that T is AnyObject, without having to add it to the other protocol?
1 Like
robnik
8
Good point! In the future I will try to reduce my example code. It definitely helped here.
1 Like
Karl
(👑🦆)
9
I see. That certainly does help.
It's actually possible to reduce the issue even further:
protocol MyProto { // add ": AnyObject" to fix
var restriction: Int { get set }
}
extension MyProto where Self: AnyObject {
func setRestriction(_ newValue: Int) {
self.restriction = newValue // Error: Cannot assign to property: 'self' is immutable
}
}
If the AnyObject constraint is part of the protocol declaration, the assignment in the extension is allowed. But if the constraint is only on the extension, we get an error.
Certainly seems like it's worth filing a bug.
3 Likes
This is actually intended behavior, consider adding the following code:
protocol MyProto2: MyProto {
init()
}
extension MyProto2 {
var restriction: Int {
get { 0 }
set { self = init() }
}
}
final class Foo: MyProto2 {
init() {}
}
In this case setting restriction is actually mutating, even though it is a class.
the reason adding : AnyObject fixes things is it changes var restriction: Int { get set } from meaning var restriction: Int { get mutating set } to var restriction: Int { get nonmutating set }
6 Likes
robnik
11
Wow, that is confusing. But I learned something new about Swift today. We can't set self in a normal property setter on a class (or in instance methods?), but we can define such a setter in a protocol extension and then "inherit" it in a class.
So basically code, like this:
obj.x = 1
... even when you know obj is an AnyObject, might reallocate a new object and change the identity of what obj points to.
I'm trying to imagine where that is useful. I don't think I've ever used it.
2 Likes
xwu
(Xiaodi Wu)
12
You’re correct, and thanks for persisting. The moderators have said that questions that happen to use SwiftUI types but come down to how the language works are on topic and absolutely welcome here. Some questions are pretty clearly not that (e.g., how to lay out a button) but when it’s not so clear I wouldn’t refrain from asking a question in these forums for fear it’s off-topic. As demonstrated here, it can lead to helpful learning.
9 Likes
young
(rtSwift)
13
The compiler suggest "mark func setRestriction(_:) mutating":
protocol MyProto/*: AnyObject*/ { // add ": AnyObject" to fix
var restriction: Int { get set }
}
extension MyProto/* where Self: AnyObject*/ {
mutating func setRestriction(_ newValue: Int) {
self.restriction = newValue // Error: Cannot assign to property: 'self' is immutable
}
}
why not just do this to solve the problem?
Jumhyn
(Frederick Kellison-Linn)
14
Making protocol members mutating makes it a bit more inconvenient to work with classes (which don't directly support mutating members) and as a corollary you can have some surprising behavior:
protocol P {
init()
}
extension P {
mutating func f() {
self = .init()
}
}
final class C: P {}
let c1 = C()
c1.f() // error: cannot use mutating member, 'c1' is a 'let' constant
let c2_let = C()
var c2_var = c2_let
c2_var.f()
c2_let === c2_var // false, the instance has changed!
3 Likes
robnik
15
Because in the original question/situation, I need to implement SwiftUI's View.body, which is not mutating.
1 Like
John_McCall
(John McCall)
Split this topic
16
John_McCall
(John McCall)
Split this topic
20
This is exactly right; thank you, Xiaodi.
(Please, no more posts about whether this is on-topic in this thread. It's on-topic.)
2 Likes