Reputation: 2635
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.
Upvotes: 1
Views: 300
Reputation: 2252
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 guard
s 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 })
If you want to abstract away how to access the employee but not the company
, then keypaths are an option as well :)
Upvotes: 2
Reputation: 11140
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]) { ... }
Upvotes: 1
Reputation: 51882
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
}
Upvotes: 1