Mutate Function Parameters in Swift

Mutate function parameters in Swift

It seems from your example that you need to change the passed arguments inside the function as a side effect, and need the updated values to be available after the function call. If that's the case, you need to use the inout modifier.

Otherwise, if all you need is to modify the parameters inside of the function call, you can explicitly define them as variables on the function definition:

Using in-out parameters

First, change your function declaration to:

func exampleFunction(inout value: String, index: inout Int) -> Bool

Now, an in-out parameter has a value that is passed in to the function, is modified by the function, and then is passed back out of the function, replacing the original value. For this to work, you can't pass a literal into your function, since there would be nowhere to store the modified value afterwards. In fact, it has to be a variable.
You cannot pass a constant or a literal value as the argument, because constants can't be modified. Hence, change your function call to:

var passed = "passedValue"
var index = 2

var b = exampleFunction(&passed, &index)

After the call, both passed and index will contain the new values, as modified by the function.

Also, note the & before each argument when calling the function. It must be there, to indicate the argument can be modified by the function.

Using variable parameters – Removed in Swift 3

In this case, all you need to do is change your function declaration to use variable parameters, as below:

func exampleFunction(var value: String, var index: Int) -> Bool

Changes made to the arguments inside the scope of the function, aren't visible outside the function or stored anywhere after the function call.

Swift make method parameter mutable?

As stated in other answers, as of Swift 3 placing var before a variable has been deprecated. Though not stated in other answers is the ability to declare an inout parameter. Think: passing in a pointer.

func reduceToZero(_ x: inout Int) {
while (x != 0) {
x = x-1
}
}

var a = 3
reduceToZero(&a)
print(a) // will print '0'

This can be particularly useful in recursion.

Apple's inout declaration guidelines can be found here.

Is there any difference between mutating function and inout parameters in Swift?

mutating marks a method. inout marks a parameter. They are completely different things.

Methods marked with mutating can mutate self i.e. set properties of self, reassign self etc.

struct Foo {
var foo: Int

mutating func mutate() {
foo += 1 // this is mutating self
}
}

Parameters marked with inout basically become var variables, as opposed to let constants. You can change them, and the changes will also reflect on the caller's side.

func f(_ x: inout Int) {
x = 10
}

var a = 1
f(&a)
print(a) // 10

Why are function parameters immutable in Swift?

The motivation for this is described here: https://github.com/apple/swift-evolution/blob/master/proposals/0003-remove-var-parameters.md

tl;dr: it avoids confusion with the inout keyword.

How to pass a class object to a function but prevent mutation?

This is extremely common throughout Cocoa. You create an immutable class and a mutable subclass. For examples, see AVComposition/AVMutableComposition, CBService/CBMutableService, CNContact/CNMutableContact.

In ObjC, this is common practice with collections as well (arrays, dictionaries, etc), but since those are value types in Swift, there's no need to use the classes (NSArray/NSMutableArray).

In Swift, rather than creating two classes, you create an immutable protocol and a class:

protocol Person: AnyObject {
var name: String { get }
var address: String { get }
}

class MutablePerson: Person {
var name: String = ""
var address: String = ""
}

Now, any function that accept Person will have an immutable object, and any function that accepts MutablePerson will be able to mutate it. This is a general pattern you can use to give different parts of your program access to different slices of the object's API. It's much more general and flexible than just const.

That said, this is not as common a pattern in Swift as it is in ObjC, since in most cases where this is useful, the type should be a struct anyway. But it is absolutely available if needed.


To your question about doing this with two classes, as in ObjC, it's possible, as long as you define both in the same file. It's just a bit tedious:

public class Person {
public fileprivate(set) var name: String = ""
public fileprivate(set) var address: String = ""
}

public class MutablePerson: Person {
public override var name: String {
get { super.name }
set { super.name = newValue }
}
public override var address: String {
get { super.address }
set { super.address = newValue }
}
}

It's possible a property wrapper could improve this, but I haven't been able to figure out how.

How to mutate the variable that passed from other views

One way to to this is by using a @Binding to hold @State in a parent view and pass it down through the view hierarchy, letting the children send data back up.

(One caveat is that sending a Binding through many views looks like it may have unexpected results in the current version of SwiftUI, but one or two levels seems to be fine. Another option is using an ObservableObject with a @Published property that gets passed between views)

Note how the ContentView owns the [Goal] and then the subsequent child views get it as a @Binding -- the $ symbol is used to pass that Binding through the parameters:

struct Goal: Identifiable {
var id: UUID
var type: String // type of goals
var name: String // the custom name of the goal
var isLongTerm: Bool // if goal is a long term goal (no deadline)
var progress: [Progress] // an array of progress for each day

init(_ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]) {
id = UUID()
type = t
name = n
isLongTerm = iLT
progress = p
}
}

struct ContentView: View {
@State var goals: [Goal] = []

var body: some View {
TabView{
VStack{
Text("You have")
Text("\(goals.count)")
Text("tasks to do")
}.tabItem { Text("Home")}
MyScroll(numRange: 1..<100, goals: $goals).tabItem { Text("My Goals") }
}
}
}

struct MyScroll: View {
var numRange: Range<Int>
@Binding var goals: [Goal]

var body: some View {
NavigationView{
VStack{
NavigationLink(destination: AddView(goals:$goals)){
Image(systemName: "folder.badge.plus")
}
List(goals) { goal in
HStack(alignment: .center){
Text(goal.name)
}
}
}
}.navigationTitle(Text("1111"))
}
}

struct AddView: View {
@Binding var goals:[Goal]
@State var types = ["study", "workout", "hobby", "habbit"]
@State private var selected = false
@State var selection = Set<String>()
@State var goalName: String = ""
@State var goalType: String = ""
@State var isLongTerm: Bool = false
@State var progress: [Progress] = []

var body: some View {
VStack{
Text("Create your goal")
// type in name
HStack{
TextField("Name", text: $goalName)
}.padding()
// choose type: a selection list
HStack{
List(types, id: \.self, selection: $selection) {
Text($0)
}
.navigationBarItems(trailing: EditButton())
}.padding()
// toggle if it is a logn term goal
HStack{
Toggle(isOn: $selected) {
Text("Is your goal Long Term (no end date)")
}.padding()
}.padding()
Button(action: {
addGoal(goalType, goalName, isLongTerm, progress)
}, label: {
/*@START_MENU_TOKEN@*/Text("Button")/*@END_MENU_TOKEN@*/
})
}
}

// function that add the goal instance to the goals
func addGoal( _ t:String, _ n:String, _ iLT: Bool, _ p: [Progress]){
let item: Goal = Goal(t,n,iLT,[])
goals.append(item)
}
}

Your addGoal function no longer has to be mutating, since it's not actually mutating its own state any more (which doesn't work in SwiftUI anyway).

As a side note, I'd be cautious about writing your initializers and functions like you're doing with the _ unnamed parameters -- I found one in your original code where you meant to be passing the name of the goal but instead were passing the type for that parameter, and because all of the parameters were/are unnamed, there's no warning about it.

Mutating function inside structure

I actually found the problem.. and it was pretty stupid.. I just had to change the "completed" part of the function, like this:

 mutating func getUserWorkspace(base: String, completed: @escaping (_ arr:[Workspace]?) -> ()){
let url = URL(string: "SOME URL")!
var request = URLRequest(url: url)

request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")

URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let rep = try JSONDecoder().decode([Workspace].self, from: data!)
DispatchQueue.main.async {
completed(rep)
}
}catch {
print(error)
}
}
}.resume()
}


Related Topics



Leave a reply



Submit