5

I've got codebase for iOS target with mixed languages in play: C++, Objective-C++ / Objective-C & now I want to start migration of Objective-C/Objective-C++ code to Swift.

Particular problem I'm tackling now is as follows:

I have an external service / framework that calls into my Objective-C++ code, let the method signature be:

- (void)reactTransactionWillMount:(const MountingTransaction &)transaction

Where MountingTransaction is a C++ struct with possibly big payload, lets say:

struct MountingTransaction {
  std::vector<Mutation> mutationList;
};

I want to now forward this call to my Swift code:

- (void)reactTransactionWillMount:(const MountingTransaction &)transaction
{
  swiftClassInstance.SIGNATURE_IN_QUESTION(transaction);
}

and avoid copying the struct payload.

My experience with Swift is very very short, I've just read in docs & watched video from Apple & it seems to me, that Swift will just do a deep copy of whole vector.

How do I avoid that?

2
  • Does Swift take the MountingTransaction by value? If so, you can't avoid the copy since transaction is const. If Swift only needs to read the values in transaction without making any changes to it, it would need to be able to take it by const reference somehow. I'm not sure if that's something Swift supports. Commented Jun 12 at 16:55
  • Can you pass a pointer? You can take the address of a reference. Commented Jun 12 at 22:22

1 Answer 1

1

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.
    • One such case is when there is a copy constructor, but no move constructor.
    • Here is a quick way to verify that a C++ type is seen as non-copyable in Swift. If the type is A, then Swift code like:
      var a = A(); var a2 = a; a.someMethod()
      
      should trigger a compilation error:
      error: 'a' used after consume
      
  • 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 )
    
    • This means that the method will need to be called as
      swiftInstance.swiftWillMount(const_cast<MountingTransaction*>(&t));
      
    • This also looks ugly semantically: the transaction is not intended to be mutated.
  • 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:

  • Same swiftWillMount() signature as in Option 1.
  • MountingTransaction and Mutation are annotated as SWIFT_UNSAFE_REFERENCE in C++.
  • Introduce a method like
    SWIFT_RETURNS_INDEPENDENT_VALUE 
    std::vector<Mutation> & MountingTransaction::getMutations()
    
    that returns a reference to the mutation list.
  • If the return value is x, you can call const or non-const methods on a Mutation at the i-th position as
    x.pointee[i].method()
    
  • Note that calling size() on the return value will cause the whole vector to be copied, so you can introduce a method on the transaction to return the vector length and then use the size to iterate over the mutation list in a for loop.

Option 3:

Signature of our Swift method:

swiftWillMount(_ t: inout MountingTransaction)
  • MountingTransaction and Mutation are not annotated with any customization macros.
  • swiftWillMount() will be made available in C++ as
    swiftWillMount(MountingTransaction & t )
    
    • This means that the method will need to be called as
      swiftInstance.swiftWillMount(const_cast<MountingTransaction&>(t));
      
    • This again looks ugly semantically: the transaction is not intended to be mutated.
  • Introduce a method like
    SWIFT_RETURNS_INDEPENDENT_VALUE 
    Mutation * MountingTransaction::getMutationAt(int idx)
    
    • UnsafeMutablePointer<Mutation> is returned.
    • Non-const Mutation methods can be invoked on the returned value x without copying the mutation object:
      x.pointee.mutating_method()
      

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)
    
    • This means that the method can be simply called as
      swiftInstance.swiftWillMount(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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.