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
}
}
}
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 .
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.
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.
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.
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 }
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.
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.
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!