Enumerate through array of structs - swift

Take a look at this code:
struct Person {
var name: String
var age: Int
}
var array = [Person(name: "John", age: 10), Person(name: "Apple", age: 20), Person(name: "Seed", age: 30)]
//for item in array { item.name = "error: cannot assign to property: 'item' is a 'let' constant" }
array[0].name = "Javert" //work fine.
I'm trying to change property's value of a struct inside a loop.
Of course I can change Person to class and it works just fine. However, I don't understand why item is a le.
Oh, I just figured this out, for item in... just created an copied of actual object inside array, that means it is automatically declared as a let constant.
So that's why I cannot change its properties value.
My question is, beside changing Person to class, how can I change Person's properties inside a loop ?
Edit:
Thank to #Zoff Dino with the original for index in 0..<array.count answers.
However, this is just a simplified question.
What I want to archive is using higher-order functions with array of structs, like:
array.each { item in ... }.map { item in ... }.sort { } ...

The old school for i in ... will do the job just fine:
for i in 0..<array.count {
array[i].name = "..."
}
Edit: higher order function you said? This will sort the array descendingly based on age:
var newArray = array.map {
Person(name: "Someone", age: $0.age)
}.sort {
$0.age > $1.age
}
print(newArray)

If you only want to change some properties of the elements you can use map and return a mutated copy of it:
array.map{ person -> Person in
var tempPerson = person
tempPerson.name = "name"
return tempPerson
}.sort{ ... }

Related

#AppStorage not updating view

I have the following reproduced example in Swift/SwiftUI.
Intended Functionality: List of numbers, where upon deletion of any number, the numbers update and reorganize so that they are in consecutive order.
Example: Suppose we have numbers 1, 2, 3, 4 in a list. When we delete number 2, the list should become 1, 2, 3 instead of staying like 1, 3, 4.
Problem: When #State is used to hold the array of numbers, the code works as expected. However, when an array in #AppStorage is used to hold the array, the view does not seem to update/change the numbers.
So, why can't I use the #AppStorage approach, and how can I fix this?
I know the code looks like a lot, but you can mostly ignore it if you just read the comments. It's just a body with a couple of functions, nothing crazy.
class MyObj: Codable, Identifiable { //SIMPLE OBJECT TO HOLD A NUMBER
init(num: Int) {
self.num = num
}
var id: UUID = UUID()
var num: Int
}
struct test2: View {
#State var array: [MyObj] = [] //USING THIS (#State) WORKS
//AppStorage("array") var array: [MyObj] = [] //USING THIS (#AppStorage) DOESN’T UPDATE NUMBERS
func minimizeNums() {
for i in 0..<array.count { //MAKES NUMBERS IN ORDER/CONSECUTIVE
array[i].num = i
}
}
var body: some View {
VStack {
Button("add number object") {
array.append(MyObj(num: array.count))
}
List {
ForEach(array) { obj in
Text(String(obj.num))
}
.onDelete(perform: { index in
array.remove(atOffsets: index) //REMOVES NUMBER OBJECT FROM LIST
minimizeNums() //MAKES THE REMAINING OBJECT'S NUMBERS CONSECUTIVE
})
}
}
}
}
Important: I used the extension from this accepted answer in order to store arrays in #AppStorage. I assume this extension may be contributing to the problem, but I'm not sure how!
This is failing, because you are using a class for your model MyObj. There are multiple reasons for using structs with SwiftUI. Please read Blog entry or any other tutorial or documentation.
[TLDR]:
Don´t use classes use structs.
Changing MyObj to:
struct MyObj: Codable, Identifiable {
init(num: Int) {
self.num = num
}
var id: UUID = UUID()
var num: Int
}
should work.

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

as shown in the code below:
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() return an immutable value, that can't be changed! So Is there any way to make the callAsFunction() method return a mutable value?
Thanks ;)
Updated:
My idea is to transfer all the calls and visits of the Group to the Person object in the Group, just like using Person directly.
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
My needs are a bit like proxy objects in the Ruby language. Accessing an object (Group) actually accesses another object (Person) inside it, as if the Group does not exist.
ruby proxy patterns:
https://refactoring.guru/design-patterns/proxy/ruby/example
CallAsFunction is the closest implementation so far, but requires that Person cannot be a Struct, otherwise it cannot be assigned to its properties.
Maybe it's not possible to implement this feature in Swift yet?
You're using the wrong dynamic method. What you want is dynamicMemberLookup. Watch closely. First, the preparation:
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 }
}
}
Now look at what that allows you to say:
var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")
Do you see? We set the name of the Group even though it has no name property, and the result was that we set the name of the Group's person which does have a name property.
The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:
struct Person {
var name: String
}
Person(name: "Foo").name = "Bar"
That returns the same error:
If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.
If you want the behavior you want, you would use a #dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.
You said:
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
You do not need “100 subscript methods.” It is the motivating idea behind #dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one #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")
If you want to handle different types, make it generic:
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 }
}
}
And
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)

Swift: how closure captures variables of value type?

Take a look at the following code snippet
struct Person{
var name: String
let surname: String
var closure: (()->())?
init(name: String, surname: String){
self.name = name
self.surname = surname
}
}
var person = Person(name: "John", surname: "Lennon")
let cl = {
print(person.name)
}
person.name = "Bill"
cl()
print(person.name)
the output of the above snippet is
Bill
Bill
Can somebody explain how this happens? I thought that since closure is reference type and Person is a value type then when the closure is created it gets its own copy of the Person(since value types are copied on pass), so modifying outer Person should not affect Person that is captured by closure, but it seems that it doesn't work in this way. I'm new to swift and value types, so please don't judge my question too hard.Thank you P.S. I know that we can capture value variable explicitly using capture list and in this case modifying outer variable doesn't affect captured variable. The question is no about this. The question is about the fact that I thought that it should be have the same way even without explicit capture
The behaviour you expect only works when you explicitly pass in a variable to a closure like this:
var person = Person(name: "John", surname: "Lennon")
let cl: (Person) -> () = { person in
print(person.name)
}
cl(person)
person.name = "Bill"
cl(person)
When you implicitly capture a variable in a closure, that variable is always passed by reference. If you want to capture variables by value, you need to explicitly pass them in.

Clear way to update some nested struct from a bigger struct in place

Say we have some complex struct with multiple nested levels(for simplicity, in the example will be only one level, but there could be more).
Example. We have a data structure:
struct Company {
var employee: [Int: Employee]
}
struct Employee {
var name: String
var age: Int
}
var company = Company(employee: [
1: Employee(name: "Makr", age: 25),
2: Employee(name: "Lysa", age: 30),
3: Employee(name: "John", age: 28)
])
Now we want to create a function which updates some Employee of the company in place. We could write it using an inout param:
func setAge(_ age: Int, forEmployee employee: inout Employee) {
employee.age = age
}
setAge(26, forEmployee: &company.employees[1]!)
This works, but as you can see we need to unwrap expression 'company.employees[1]' before passing it by ref. This forced unwrap can produce runtime error if there is no such employee for the provided key.
So we need to check if the employee exists:
if company.employees[1] != nil {
setAge(26, forEmployee: &company.employees[1]!)
}
This also works, but this code is kind of ugly because we need to repeat the expression 'company.employees[1]' two times.
So the question is: Is there some way to get rid of this repetition?
I tried to use optional inout param in the modifying function but could not get it working.
Based on your comments, like
What I wanted in the first place is just to have a reference to a substructure of a bigger structure so the part of code that is dealing with the substructure could know nothing about where is this substructure located in the bigger structure.
and
It would be ideal if I just could create a local inout var. Like if var employ: inout Employee? = company.employee[1] { // make whatever I want with that employee }.
I think that what you want is a generic update function. In the community this is part of the family of utility functions referred as with (https://forums.swift.org/t/circling-back-to-with/2766)
The version that you need in this case is one that basically guards on nil, so I suggest something like
func performUpdateIfSome <T> (_ value: inout T?, update: (inout T) throws -> Void) rethrows {
guard var _value = value else { return }
try update(&_value)
value = _value
}
with this utility then what you wanted to do would be done with
performUpdateIfSome(&company.employees[1], update: { $0.age = 26 })
Note
If you want to abstract away how to access the employee but not the company, then keypaths are an option as well :)
You need to hide the implementation and let the struct handle the logic with specific error handling strategy, like throwing an error or simply return true/false depending on success or simply ignore any problems. I don't know what the Int key stands for but here I guess it's an ID of some sort, so add this to Company struct
mutating func setAge(_ age: Int, forId id: Int) -> Bool {
if employee.keys.contains(id) {
employee[id]?.age = age
return true
}
return false
}
I would simply add extension to Employee which set employee's age
extension Employee {
mutating func setAge(_ age: Int) {
self.age = age
}
}
Then you can use optional chaining for calling. So if value for key 1 doesn't exist, nothing happens and code goes on
company.employee[1]?.setAge(26)
Edit:
If your goal is just to change some property and then return object, simply create method which takes optional parameter and returns optional value
func setAge(_ age: Int, forEmployee employee: inout Employee?) -> Employee? {
employee?.age = age
return employee
}
if let employee = setAge(26, forEmployee: &company.employees[1]) { ... }

Swift variable value back to original after loop

I have this simple code:
for index in 0..<people.count {
var person = people[index]
var home = homes[index]
person.home = home
println("\(person.home)")
}
for index in 0..<people.count {
var person = people[index]
println("\(person.home)")
}
Person:
struct Person: Deserializable {
var home: Home?
init() { }
init(data: [String : AnyObject]) {
home <-- data["home"]
}
}
In the println statement in first loop, it assigns the home to it's respective person and prints the proper home.
In the second loop, it prints nil (back to normal). It's almost as if the first loop has no effect.
I have no idea how to debug this. Please help
You do not give any information about what person is. But suppose it is a struct. Then I would not expect there to be any effect on the people array's persons, because the person in var person = people[index] is a copy.
So in that case you would need an extra line in your first loop, writing the altered person back into the array:
people[index] = person