Is Swift Inout Parameter a Variable or a Pointer

Is swift inout parameter a variable or a pointer?

Being a pointer is only side-effect of optimization process for inout parameters. They actually work in different manner using copy-in copy-out behavior. So inside function that parameter is treated just like regular variable, not pointer. If you pass it to another function that takes inout parameter you have to mark it as such.

In-out parameters are passed as follows:

When the function is called, the value of the argument is copied.

In the body of the function, the copy is modified.

When the function returns, the copy’s value is assigned to the original argument.

This
behavior is known as copy-in copy-out or call by value result. For
example, when a computed property or a property with observers is
passed as an in-out parameter, its getter is called as part of the
function call and its setter is called as part of the function return.

As an optimization, when the argument is a value stored at a physical
address in memory, the same memory location is used both inside and
outside the function body. The optimized behavior is known as call by
reference; it satisfies all of the requirements of the copy-in
copy-out model while removing the overhead of copying. Write your code
using the model given by copy-in copy-out, without depending on the
call-by-reference optimization, so that it behaves correctly with or
without the optimization.

In-Out Parameters

Declare a pointer to a property and pass it as inout param into func in Swift?

After discussion in comments, the question is intended to be specifically how to work with UnsafeMutablePointer to achieve the result, and less about the best way to achieve the result.

It's important that the entirety of the code from the point of acquiring the pointer to using it be scoped within withUnsafeMutablePointer. Because it comes down to selecting between two arrays, and then passing one of them via an alias to someFunc, you don't know which pointer has to be kept live, so you have to keep them both live. Otherwise your program will likely crash when Swift invalidates it.

The proper and safe way to achieve the desired effect with pointers is like this:

func pointerSolution()
{
var sourceArray1 = [1, 2, 3, 4]
var sourceArray2 = [5, 6]

func someFunc(_ someArray: inout [Int]) {
someArray.indices.forEach { someArray[$0] = 0 }
}

let someCase = true // just for the sake of compiling a concrete case

//usage code
withUnsafeMutablePointer(to: &sourceArray1)
{ src1 in
withUnsafeMutablePointer(to: &sourceArray2)
{ src2 in
// Substitute appropriate type for `Int`
let arrayPtr: UnsafeMutablePointer<[Int]>
if someCase {
arrayPtr = src1
// Some additional code that may or may not involve arrayPtr
} else {
arrayPtr = src2
// Some additional code that may or may not involve arrayPtr
}
someFunc(&arrayPtr.pointee)
}
}

print("sourceArray1 = \(sourceArray1)")
print("sourceArray2 = \(sourceArray2)")
}

If you have to do this is in multiple places, or just want to clean up the syntactic bloat of nesting withUnsafeMutablePointer blocks, you can provide a helper function:

func withUnsafeMutablePointers<T, R>(
to value1: inout T,
and value2: inout T,
_ body: (UnsafeMutablePointer<T>, UnsafeMutablePointer<T>) throws -> R) rethrows -> R
{
try withUnsafeMutablePointer(to: &value1)
{ ptr1 in
try withUnsafeMutablePointer(to: &value2)
{ ptr2 in
try body(ptr1, ptr2)
}
}
}

Then where you use it, you have one level of nesting:

func pointerSolution()
{
var sourceArray1 = [1, 2, 3, 4]
var sourceArray2 = [5, 6]

func someFunc(_ someArray: inout [Int]) {
someArray.indices.forEach { someArray[$0] = 0 }
}

let someCase = true // just for the sake of compiling a concrete case

//usage code
withUnsafeMutablePointers(to: &sourceArray1, and: &sourceArray2)
{ src1, src2 in
// Substitute appropriate type for `Int`
let arrayPtr: UnsafeMutablePointer<[Int]>
if someCase {
arrayPtr = src1
// Some additional code that may or may not involve arrayPtr
} else {
arrayPtr = src2
// Some additional code that may or may not involve arrayPtr
}
someFunc(&arrayPtr.pointee)
}

print("sourceArray1 = \(sourceArray1)")
print("sourceArray2 = \(sourceArray2)")
}

If you want to live dangerously, you can do this:

func dangerousPointerSolution()
{
var sourceArray1 = [1, 2, 3, 4]
var sourceArray2 = [5, 6]

func someFunc(_ someArray: inout [Int]) {
someArray.indices.forEach { someArray[$0] = 0 }
}

let someCase = true // just for the sake of compiling a concrete case

//usage code
let address: Int
if someCase {
address = withUnsafeMutablePointer(to: &sourceArray1) { Int(bitPattern: $0) }
// Some additional code that may or may not involve address
} else {
address = withUnsafeMutablePointer(to: &sourceArray2) { Int(bitPattern: $0) }
// Some additional code that may or may not involve address
}
someFunc(&(UnsafeMutablePointer<[Int]>(bitPattern: address)!).pointee)

print("sourceArray1 = \(sourceArray1)")
print("sourceArray2 = \(sourceArray2)")
}

Note the pointer conversion through Int. This is because when withUnsafeMutablePointer returns, it invalidates the pointer $0 internally, and if you were to just return $0, the pointer that is returned by withUnsafeMutablePointer is also invalidated. So you have to trick Swift into giving you something you can use outside of withUnsafeMutablePointer. By converting it to an Int, you're basically saving off the valid address as a numeric value. Swift can't invalidate it. Then outside of withUnsafeMutablePointer you have to convert that Int address back into a pointer, which UnsafeMutablePointer<T> has an initializer to do (after all, you can imagine an embedded system with memory mapped I/O. You'd need to read/write to a specific address to do I/O.) Any time you have to trick the compiler into letting you do something, it should be a big red flag that maybe you shouldn't be doing that. You may still have good reasons, but at the very least, it should cause you to question them, and consider alternatives.

It's important that you don't use address to reconstruct another pointer outside of this scope. In this specific example, it remains a valid address within the function scope only because it's an address for local values that are referred to after the pointer is used. When those values go out of scope, using any pointer to them becomes a problem. If they were properties of a class letting the address escape outside of the scope of the class would be a problem when the instance is deinitialized. For a struct the problem happens sooner since it's likely it would end up being used on a copy of the struct rather than on the original instance.

In short, when using pointers, keep them as local as possible, and be sure they, or anything that can be used to reconstruct them without the original Swift "object" they point to, don't escape outside the context in which you know for sure that they are valid. This isn't C. You don't have as much control over the lifetime of allocated memory. Normally in Swift you don't have to worry about it, but when you do use pointers, it's actually harder to reason about their validity than it is in C for the very reason that you don't get to specify when allocated memory becomes invalid. For example, in Swift, it's not guaranteed that a locally allocated instance of a class will remain "live" through the end of the scope. In fact, it's often deinitialized immediately after its last use, even though there may be more code after in the same scope. If you have a pointer to such an object, even when you are still the same scope, you may now be pointing to deinitialized memory. Swift even has to provide withExtendedLifetime to deal with such cases. That's why Swift tries to limit their use solely within the withUnsafePointer family of functions. That's the only context that it can guarantee their validity. There are other contexts in which they would be valid, but the compiler can't prove that they are.

When to use inout parameters?

inout means that modifying the local variable will also modify the passed-in parameters. Without it, the passed-in parameters will remain the same value. Trying to think of reference type when you are using inout and value type without using it.

For example:

import UIKit

var num1: Int = 1
var char1: Character = "a"

func changeNumber(var num: Int) {
num = 2
print(num) // 2
print(num1) // 1
}
changeNumber(num1)

func changeChar(inout char: Character) {
char = "b"
print(char) // b
print(char1) // b
}
changeChar(&char1)

A good use case will be swap function that it will modify the passed-in parameters.

Swift 3+ Note: Starting in Swift 3, the inout keyword must come after the colon and before the type. For example, Swift 3+ now requires func changeChar(char: inout Character).

Swift/iOS: How to use inout parameters in functions with AnyObject/Any or Pointers

Better you can create a generic method like below:

func setValue<T>(inout object:T, key: String) {
switch key {
case "String":
object = ("A String" as? T)!
case "UIColor":
object = (UIColor.whiteColor() as? T)!
case "Bool":
object = (true as? T)!
default:
println("Unhandled key: \(key)")
}
}

And calling will be like this:

setValue(&string, key: "String")
setValue(&color, key: "UIColor")
setValue(&bool, key: "Bool")

Hope it helps!

Inout in swift and reference type

Suppose we wrote the hypothetical function you're talking about:

class C {}

func swapTwoC(_ lhs: C, rhs: C) {
let originalLHS = lhs
lhs = rhs
rhs = originalLHS
}

The immediate problem is that lhs and rhs are immutable. To mutate them, we would need to make mutable copies:

func swapTwoC(_ lhs: C, rhs: C) {
var lhs = lhs; var rhs = rhs
let originalLHS = lhs
lhs = rhs
rhs = originalLHS
}

But now the problem is that we're mutating our copies, and not the original references our caller gave us.

More fundamentally, the issue is that when you pass a reference (to an instance of a class, which we call an object) to a function, the reference itself is copied (it behaves like a value type). If that function changes the value of the reference, it's only mutating it own local copy, as we saw.

When you have an inout C, and you pass in &myObject, what you're actually passing in is a reference to your reference to myObject. When the function arguments are copied, what's copied is this "ref to a ref". The function can then use that "ref to a ref" to assign a new value to the reference myObject the caller has

Is it possible to point class instance variables to `inout` parameters in Swift?

Short Answer

No.

Long Answer

There are other approaches that can satisfy this.

Binding Variables

In SwiftUI we use Binding Variables to do stuff like this. When the Binding variable updates, it also updates the bound variable. I'm not sure if it will work in Sprite Kit.

class X {
var mySwitch: Binding<Bool>

init(_ someSwitch: Binding<Bool>) {
self.mySwitch = someSwitch
}

func toggle() { mySwitch.wrappedValue.toggle() }
}

struct Y {
@State var mySwitch: Bool = false
lazy var switchHandler = X($mySwitch)
}

Callbacks

We can add a callback to X and call it on didSet of the boolean.

class X {
var mySwitch: Bool {
didSet { self.callback(mySwitch) } // hands the new value back to the call site in Y
}

let callback: (Bool) -> Void

init(_ someSwitch: Bool, _ callback: @escaping (Bool) -> Void) {
self.mySwitch = someSwitch
self.callback = callback
}

func toggle() { mySwitch = !mySwitch } // explicitly set to trigger didSet
}

class Y {
var mySwitch: Bool = false
lazy var switchHandler = X(mySwitch) {
self.mySwitch = $0 // this is where we update the local value
}
}

Inout vs pass by reference swift optimization

As many know, inout in swift does NOT pass by reference, rather it makes an initial copy to the function then copies the values back on the return. This sounds expensive.

It's not. It's copying an instance of a [Int: Bool] (a.k.a. Dictionary<Int, Bool>) which is only a single pointer. See for yourself, with print(MemoryLayout<[Int: Bool]>.size).

Does inout/var parameter make any difference with reference type?

The difference is that when you pass a by-reference parameter as a var, you are free to change everything that can be changed inside the passed object, but you have no way of changing the object for an entirely different one.

Here is a code example illustrating this:

class MyClass {
private var testInt : Int
init(x : Int) {
testInt = x
}
}

func testInoutParameterObject(inout testClass: MyClass) {
testClass = MyClass(x:123)
}

var testClass = MyClass(x:321)
println(testClass.testInt)
testInoutParameterObject(&testClass)
println(testClass.testInt)

Here, the code inside testInoutParameterObject sets an entirely new MyClass object into the testClass variable that is passed to it. In Objective-C terms this loosely corresponds to passing a pointer to a pointer (two asterisks) vs. passing a pointer (one asterisk).

Inout and NSMutableDictionary

This works not because Cocoa types are exempt, but because NSMutableDictionary is a class (as opposed to a struct), and the inout does not refer to what you might be thinking.

Unfortunately, the documentation you link to (and the more in-depth documentation on inout parameters it links to) doesn't make it clear what "value" really means:

An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value

The following statement hints at it a little, but could be clearer:

You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified.

The "value" the documentation describes is the variable being passed as inout. For value types (structs), this is meaningful because every variable holding a value of those types effectively holds a copy of that value.

var a = MyGreatStruct(...)
var b = a
// a and b are not directly linked in any way

Passing a struct to a function normally copies the value into a new local variable (new variable = copy), whereas you can imagine inout giving you direct access to the original variable (no new variable).

What's not described is that the effect is identical for classes, which behave differently.

let a = MyGreatClass(...)
let b = a
// modifying `a` will modify `b` too since both point to the same instance

Passing a class to a function also copies the variable into a new local variable, but the copy isn't meaningful — both variables hold the same thing: a reference to the object itself in memory. Copying in that sense doesn't do anything special, and you can modify the object from inside of the function the same way you could from outside. inout for classes behaves the same way as for structs: it passes the original variable in by reference. This has no bearing on the majority of the operations you'd want to perform on the object anyway (though it does allow you to make the variable point to a different object from within the function):

var a = MyGreatClass("Foo")

// func foo(_ value: MyGreatClass) {
// value = MyGreatClass("Bar") // <- not allowed since `value` isn't mutable
// }

func foo(_ value: inout MyGreatClass) {
value = MyGreatClass("Bar")
}

print(ObjectIdentifier(a)) // <some pointer>
foo(&a)
print(ObjectIdentifier(a)) // <some other pointer>

How can I change an inout parameter from within a escaping closure?

inout is specified to have a "copy-in copy-out" behaviour. The value of x is copied when you call the function. In the function body, the copy of x can be modified, or whatever. Then, when the function returns, the copy is copied again, and assigned to the original x. "Call-by-reference" could happen as an optimisation.

This explains why you can't modify an inout parameter in an escaping closure. The swift compiler can't possibly know when every escaping closure returns, to copy the modified value back.

You can use an actual pointer:

func testAdd(v: UnsafeMutablePointer<Int>) {
addCompletion{
v.pointee = 1 // you just need to change all references to "v" to "v.pointee"
print("hello1 \(v.pointee)")
}
}

Alternatively, if you don't like pointers, you can also do this trick (adapted from my other answer):

func testAdd(modifyV: @escaping ((inout Int) -> Void) -> Void) {
addCompletion{
modifyV { v in // here you can change v however you like!
v = 1
print("hello1 \(v)")
}
}
}

You just need to change the caller to:

testAdd { $0(&x) }


Related Topics



Leave a reply



Submit