This is an update that adds yet another approach (Option 4) and is applicable to Swift 6.1 (Xcode 16) and Swift 6.2 (Xcode 26). Swift-C++ interop is still in its early stages, so I will try to continue updating it as needed.
Swift tends to copy objects around a lot and reducing this copying for performance may be tricky. Not knowing more details of what you are working on, I cannot offer one specific solution, but here are some ideas for addressing the problem and validating the approach you choose.
Let's say the name of the Swift method the call will be forwarded to is swiftWillMount(). Avoiding a transaction copy at the time of invocation is only part of the problem. With method signatures suggested below, this copying does not happen. However, the transaction may be copied inside the method if, for example, it was passed in as a mutable parameter, but a nonmutating method is invoked on it. Also, Mutation objects can get copied depending on how they are accessed.
We will use the specific case described in the question and will be focusing on avoiding the copying of mutations in the mutationList. Since a transaction contains a vector of mutations, we also want to avoid copying transactions. Whatever other types exist in the hierarchy should be handled in a similar manner.
First, a few general observations about copying of C++ objects exposed to Swift and how to reduce it.
- In Swift, immutable/const objects tend to be copied more than mutable/non-const.
- If
x is UnsafePointer to a C++ type not annotated with an appropriate special customization macro (e.g. SWIFT_NONCOPYABLE), then accessing x.pointee will copy the object pointed to.
- This copying does not occur if
x is UnsafeMutablePointer.
- To avoid copying object
x when calling a method on it
- Call mutating/non-const methods if
x is mutable/non-const.
- Call nonmutating/const methods if
x is immutable/const.
- This limitation does not apply if
x is of a C++ type annotated with SWIFT_UNSAFE_REFERENCE, SWIFT_IMMORTAL_REFERENCE, SWIFT_SHARED_REFERENCE, or SWIFT_NONCOPYABLE; i.e. in this case one can call a const method on a non-const object without causing the object to be copied.
- If a C++ type is annotated with
SWIFT_UNSAFE_REFERENCE, SWIFT_IMMORTAL_REFERENCE, or SWIFT_SHARED_REFERENCE:
- C++ functions that return or take such types other than by pointer or reference are unavailable in Swift.
- If a pointer or reference to a const of such type is returned from a C++ function, const-ness is ignored in Swift and mutating methods can be invoked on the return value.
- Swift functions that take parameters of C++ types declared with
SWIFT_NONCOPYABLE do not appear to be callable from (Objective-)C++ yet; using borrowing, consuming, or inout ownership specifiers doesn't help.
- There seem to be cases when C++ types declared
SWIFT_NONCOPYABLE don't behave as non-copyable in Swift.
- Iterating over a C++ vector in Swift using a
forEach() method actually causes at least as much copying of the elements as using a for-in loop, though it should reduce copying according to documentation (https://www.swift.org/documentation/cxx-interop/#working-with-c-containers).
Let's consider the following approaches to minimize the copying. If you don't want to or cannot change the pre-existing C++ code to utilize one of these approaches, you can use wrapper types around MountingTransaction, Mutation, and other types in your hierarchy.
Option 1:
Signature of our Swift method:
swiftWillMount(_ t: MountingTransaction)
MountingTransaction is annotated as SWIFT_UNSAFE_REFERENCE in C++.
Mutation is annotated as SWIFT_NONCOPYABLE in C++.
- Because
MountingTransaction is SWIFT_UNSAFE_REFERENCE, swiftWillMount() will be made available in C++ as
swiftWillMount(MountingTransaction *_Nonnull t )
- Introduce a method like
SWIFT_RETURNS_INDEPENDENT_VALUE
const Mutation * MountingTransaction::getMutationAt(int idx) const
that returns a pointer to the Mutation at index idx.
- Note that we need the
SWIFT_RETURNS_INDEPENDENT_VALUE annotation.
- Now we can invoke nonmutating/const
Mutation methods via the returned pointer, let's say x:
x.pointee.nonmutating_method()
Option 2:
Option 3:
Signature of our Swift method:
swiftWillMount(_ t: inout MountingTransaction)
Option 4:
Signature of our Swift method:
swiftWillMount(_ t: MountingTransaction)
MountingTransaction is not annotated with any customization macro in C++.
Mutation is annotated as SWIFT_NONCOPYABLE in C++.
- Now
swiftWillMount() will be made available in C++ as
swiftWillMount(const MountingTransaction& t)
- Introduce a method like
SWIFT_RETURNS_INDEPENDENT_VALUE
const Mutation * MountingTransaction::getMutationAt(int idx) const
that returns a pointer to the Mutation at index idx.
- Note that we need the
SWIFT_RETURNS_INDEPENDENT_VALUE annotation.
- Now we can invoke nonmutating/const
Mutation methods via the returned pointer, let's say x:
x.pointee.nonmutating_method()
As we can see, the above options are not very elegant semantically: const-ness of the transaction reference passed to the Objective-C++ method is cast-away using const_cast, though in Option 1 we are still restricted to calling nonmutating Mutation methods. There are other approaches one can come up with based on the general observations presented above.
Debugging/validation suggestions:
- To see how a Swift method is presented for use in C++, one can consult the
...-Swift.h header generated by Xcode.
- Insert code into special member functions (constructor, copy constructor, and the like) to output a message when the functions are invoked.
- Add a function to a C++ type of interest to output the address of the object; it can be invoked from both C++ and Swift to see if the object has been copied to a different location.
Hopefully your will find the above suggestions, based on experimentation and browsing the docs, useful. You can read the C++/Swift interop documentation at https://www.swift.org/documentation/cxx-interop/
Please be careful with memory management and object lifetime, those are not covered here for brevity. The various SWIFT_* customization macros can make the situation even trickier.
MountingTransactionby value? If so, you can't avoid the copy sincetransactionisconst. If Swift only needs to read the values intransactionwithout making any changes to it, it would need to be able to take it byconstreference somehow. I'm not sure if that's something Swift supports.