I need to choose one of some properties and pass it by reference to set it inside the func. Approximate code:
var someProperty = [SomeClass]()
var someProperty2 = [SomeClass]()
func someFunc(someObject: inout [SomeClass]) {
...
someObject = ... //
}
//usage code
let obj: [SomeClass]
if someCase {
obj = self.someProperty
...
} else {
obj = self.someProperty2
...
}
someFunc(&obj)
The problem is obj
is cannot be used as inout
parameter but even if I declare it as var
then obj
is changed but not someProperty
or someProperty2
.
I read that somehow I need to declare obj
as UnsafeMutablePointer
and there are similar questions. But I don't know how to apply them to the code above to fix only these 3 lines (without of changing the rest of code):
let obj: [SomeClass]
obj = self.someProperty
obj = self.someProperty2
How to solve this issue?
P.S. in other words I need something like let obj: inout [SomeClass]
but it is not allowed by 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.