Yes, but maybe I am too dramatic by calling it unreliable. Let's say we have a cat class:
class cat final {
private:
std::atomic<size_t> numReferences;
// fetch_sub returns value before subtraction
size_t decrement() { return numReferences.fetch_sub(1) - 1; }
cat(): numReferences(1) { }
~cat() { }
public:
static cat* nonnull create() {
return new cat();
}
void retain() {
printf("Retained: %zu\n", numReferences.load() + 1);
numReferences++;
}
void release() {
printf("Released: %zu\n", numReferences.load() - 1);
if (decrement() == 0) {
delete this;
}
}
void meow() { printf("meow\n"); }
} SWIFT_SHARED_REFERENCE(retainCat, releaseCat);
cat* nonnull createCat() {
return cat::create();
}
void retainCat(cat* nonnull aCat) {
aCat->retain();
}
void releaseCat(cat* nonnull aCat) {
aCat->release();
}
It's ported to Swift by providing the retainCat and releaseCat functions.
If I create a cat from a global (or from a namespace) C++ function, Swift will only decrease the reference count after exiting the scope, which perfectly suits for my particular use case. The object then gets released, because object was created and returned with reference count of 1, and after exiting the scope it's decremented to 0, so it will be destroyed.
func testFunction() {
// A cat is created with reference count of 1
let aCat = createCat();
// print: Retained: 2
aCat.meow();
// print: Released: 1
// print: Released: 0
// aCat is destroyed
}
But when I create an object from a class' static C++ method, the Swift will increment the number of references right after receiving the object (which I didn't expect, didn't want and don't have control over) and decrement after exiting the function, leaving aCat with reference count of 1 and thus causing a memory leak.
func testStaticMethod() {
// A cat is created with reference count of 1
// print: Retained: 2
let aCat = cat.create();
// print: Retained: 3
aCat.meow();
// print: Released: 2
// print: Released: 1
// aCat is leaked
}
As workaround, I don't use C++'s static methods in Swift code, because it never works the way I want.
Another example, if you get A C++ object from the B C++ object using B's getter, the reference count of A will also be increased after receiving the object, which usually is what we need. But what if B already returns A with increased number of references? This is especially suitable when B is a thread-safe class, and it increases A's number of references inside B's getter's synchronization logic to make sure that A will not be destroyed by another thread while returning it from getter from the current thread. Swift doesn't know it and applies default behavior.
All these problems could've been solved if the methods or functions could provide annotations that allow Swift to understand if it should retain the object after receiving it from a function or method or not, like SWIFT_RETURNS_RETAINED/UNRETAINED would be enough.
Another problem that I faced, is when, in Swift code, I try to obtain a C++ object by accessing an Objective-C object's property - I get a runtime error. It looks like Objective-C tries to call objc_retain function by passing a C++ object, which is definitely wrong. As workaround, I provide a global function that accepts an Objective-C object, calls its getter and returns the value. I've even filed FB13297213 via Feedback Assistant, provided a verbose description and a working example code, and of course I got ghosted since October 2023.
One more problem, the __attribute__((objc_direct_members))
attribute (that is attached to an Objective-C interface) causes compiler to cough and gag and puke with some strange compiler errors if I tried to access its C++ properties via Swift code. I'm not sure if this particular problem was solved, but I uncommented this attribute later after some Xcode updates and it at least compiles, but I still don't access its C++ members in Swift code.
So yeah, I think, I'm just being dramatic, because despite of all these problems, the C++ interoperability works solid, but with many pitfalls and gets you really frustrated if you just started to work with it.