I return an instance from a get-only subscript, but I cannot assign to a property of the returned instance
struct Student {
var id: String
var attendance = 0
var absence = 0
}
class StudentStore {
var students = [Student]()
subscript(id: String) -> Student? {
students.first(where: { $0.id == id } )
}
}
var studentStore = StudentStore()
var newStudent = Student(id: "2014901001")
studentStore.students.append(newStudent)
studentStore["2014901001"]?.attendance = 1 // error: Cannot assign to property: subscript is get-only
CodePudding user response:
The subscription doesn't have a set
ter. What you have is the implicit get
ter which is a short form of
subscript(id: String) -> Student? {
get {
students.first(where: { $0.id == id } )
}
}
But it's impossible to update a value type item in an array with key subscription anyway.
An alternative is to add an update function to the store which takes the id
, a keyPath
and a value
func updateStudent<Value>(withID id: String, keyPath: WritableKeyPath<Student,Value>, value: Value) {
guard let index = students.firstIndex(where: {$0.id == id}) else { return }
students[index][keyPath: keyPath] = value
}
Getting the index is mandatory to be able to modify the item in the array directly.
Then you can replace
studentStore["2014901001"]?.attendance = 1
with
studentStore.updateStudent(withID: "2014901001", keyPath: \.attendance, value:1)
CodePudding user response:
Error show exactly what you have to change. Because subcript
will return something likes:
let updateStudent = studentStore["studentStore"]
You can not change with
let
So update with this:
if var updateStudent = studentStore["studentStore"] {
updateStudent.attendance = 1
}
CodePudding user response:
When you do
studentStore["2014901001"]?.attendance = 1
You are trying to set one of the Student
objects in the students
array, aren't you?
However, the subscript getter doesn't return (a reference to) one of the objects in the students
array. Due to the value semantics of the Student
struct, you return a copy of the Student
object that is in the students
array. You can't achieve what you want if you just set the attendance
of that copy. Therefore, the Swift compiler compiles your code to something like this:
let copy = studentStore["2014901001"]
copy?.attendance = 1
studentStore["2014901001"] = copy
You can see that it sets the setter with the modified copy again. But your subscript does not have a setter, which is where the error comes from.
To solve this, you can make Student
a class, which has reference semantics. This enables you to return the Student
object that is in the students
array, allowing the caller of the subscript to set their attendance.
class Student {
var id: String
var attendance = 0
var absence = 0
init(id: String, attendance: Int = 0, absence: Int = 0) {
self.id = id
self.attendance = attendance
self.absence = absence
}
}
Alternatively, you can add a setter. However, that would allow nonsense like this:
studentStore["1"] = Student(id: "2")
You need to decide what you want to happen here.