Questions on how to use Objective C in JUCE-based C++ classes (Memory management, autoreleaspools, juce_osx_ObjCHelpers)

Okay, I started this thread yesterday trying to fix an error in some Objective C wrapper code I wrote. However, to me it seems that I’m lacking some basic knowledge on how to write such a class in a proper way.

So instead of asking for help for my specific error I want to ask differently: Can someone give a quick outline of how to implement such a class correctly?

Maybe you could guide me with some example. Let’s say I want to create some kind of string array class that uses an NSArray internally. I know this is nothing really senseful but it might serve as some good example for some of my questions

// StringArrayUsingNSArray.h

#include <juce_core/juce_core.h>

class StringArrayUsingNSArray
{
public:
    StringArrayUsingNSArray (juce::StringArray& arrayToInitializeFrom);
    juce::String getStringAtIdx (int i);

private:
    class Pimpl;
    std::unique_ptr<Pimpl> pimpl;
};

// StringArrayUsingNSArray.mm

#import <Foundation/Foundation.h>
#include <juce_core/native/juce_osx_ObjCHelpers.h>
#include "StringArrayUsingNSArray.h"

class StringArrayUsingNSArray::Pimpl
{
public:
    Pimpl (juce::StringArray& arrayToInitializeFrom)
    {
        NSMutableArray* mutableArray = [NSMutableArray new]; // any autorelease needed here?
        for (auto& string : arrayToInitializeFrom)
            [mutableArray addObject: juce::juceStringToNS (string)];

        nsArray = mutableArray; // what about object ownership here?
    }
    
    ~Pimpl ()
    {
        // Any cleanup needed here?
    }

    juce::String getStringAtIdx (int i)
    {
        return juce::nsStringToJuce (nsArray[static_cast<NSUInteger> (i)]);
    }

private:
    NSArray* nsArray;
};

StringArrayUsingNSArray::StringArrayUsingNSArray (juce::StringArray& arrayToInitializeFrom)
{
    pimpl.reset (new Pimpl (arrayToInitializeFrom));
}

juce::String StringArrayUsingNSArray::getStringAtIdx (int i)
{
    return pimpl->getStringAtIdx (i);
}

First Topic: Memory management / Object lifetime of Cocoa class Members
I read a lot regarding the autorelease mechanisms and it just feels quite difficult for me as a C++ thinking person to get a feeling for what happens when. To me as a C++ guy there should be something like [nsArray release] in the destructor of my Pimpl class.

However, there is this autoreleasepool that every juce thread creates (if I got it right). Now is the nsArray object created in the constructor automatically added to the autoreleasepool of the thread invoking this constructor? What happens if the constructor is invoked on a different thread than the desctructor or the autoreleasepool of the constructing thread goes out of scope while the class is still accessed by other threads?

Second Topic: Memory management / Object lifetime of temporary created objcets
I’ve seen inside juceStringToNS that it returns the NSString with an autorelease call, so who is responsible to delete this string after it has been added to the array? Is the string copied to the array and then the temporary created string is released by the autoreleasepool of the thread that invokes the constructor? Or will the ownership of the string be transferred to the array and it will release the string when the array is released? Or is it something completely different?

And is the assignment of the temporary created mutable array to the array class member correct?

Third Topic: Usage of juce_osx_ObjCHelpers.h
Including the juce_osx_ObjCHelpers.h file like above generates the following compiler errors:

Error:(204, 45) use of undeclared identifier 'objc_msgSendSuper'; did you mean 'ObjCMsgSendSuper'?
Error:(210, 95) use of undeclared identifier 'objc_msgSendSuper'
Error:(214, 95) use of undeclared identifier 'objc_msgSend_fpret'
Error:(292, 20) variable has incomplete type 'juce::objc_super'
Error:(301, 9) use of undeclared identifier 'object_getInstanceVariable'
Error:(341, 20) variable has incomplete type 'juce::objc_super'
Error:(353, 13) use of undeclared identifier 'object_setInstanceVariable'
Error:(356, 20) variable has incomplete type 'juce::objc_super'

Seems like I’m using it somehow wrong. What’s the right way to include this header? Note that all this is implemented in the context of a custom juce module.

I’d really love to get some answers to gain a bit more understanding of all this Obj-C memory management stuff and how it bridges cleanly to a JUCE-based C++ context.

I would love to know the solution to the third topic as well!
Any clues?

I’ll try and answer this since nobody else has, but manual memory management was deprecated on macOS 7 years ago or so I’ve gotten pretty rusty.

The simple rule is anything you new/alloc/init/retain you must release. When reference count hits 0 it gets deleted.

So in the destructor of Pimpl there needs to be a [nsArray release]

So, with this system, how would you write a function that returns a new object like juceStringToNS? Since the function name doesn’t start with new/alloc/init then the object should not be released by the caller.

You could do:

static inline NSString* juceStringToNS (const String& s)
{
    return [[[NSString alloc] initWithUTF8String: s.toUTF8()] autorelease];
}

The alloc/release are balanced, so no memory leaks. However, if you used release here, reference count would hit 0 and object would be deleted and you’d return garbage and crash. Instead autorelease means decrement reference count some time later. So if caller of the function just needs the object temporarily they can use it for a bit and it’s fine. If they need to keep it for longer they can retain it, or at it to the NSArray, which will retain all the objects in it.

The autorelease pool does not release things for you, you are still required to match your alloc/retain/new with a release/autorelease. The autorelease pool just looks for objects that have been autoreleased and call release once per time autorelease has been called.

Calling autorelease adds the object to the current threads pool, so you don’t need to worry about other threads deleting your objects.

For the errors, I think #import <objc/objc-runtime.h> is missing.

3 Likes

Although this comes a bit late I want to say thank you for your answer, I think as it came approx 9 months after the creation of the topic I just didn’t pay that much attention to it because the Obj-C task I faced back then had been finished for some while.

However I’m just starting a new project that interfaces with Obj-C APIs, came back with some same questions to this thread and found this answer to be very helpful to get things done right from the the start :+1:

1 Like

[ Edit ] - Apologies – Solved