Search code examples
cswiftpointersstructfoundation

Swift: Accessing C Struct Members That Are Objects


What I Have Right Now:

In my app, I have a global C struct that holds colors:

//Colors.h

extern struct MYColors *appColors;

struct MYColors
{
    CGColorRef appBackgroundColor;
    // ...Lots more colors follow
};

And the matching implementation file:

//Colors.m

struct MYColors *appColors = calloc(1, sizeof(struct MYColors));
appColors->appBackgroundColor = CGColorCreateGenericRGB(23.0f/255.0f, 24.0f/255.0f, 26.0f/255.0f, 1.0f);

This allows me to centralize all my app's colors. In various custom views, I write code like this in Objective-C:

- (void) updateLayer {
    someCGLayer.backgroundColor = appColors->appBackgroundColor;
}

What I Need:

I'm starting to migrate this app to Swift and I have not been able to figure out how I can access the imported version of this C Struct. I've seen lots of posts for simple structs that contain int, float, etc.

If I have a global instance (basically a singleton) of this struct, appColors, how do I access the members of that struct from Swift?


What I Thought Would Work:

This does not work. Swift claims that MYColors has no appBackgroundColor:

let color: CGColor = UnsafePointer<MYColors>(appColors).appBackgroundColor

I also thought maybe I just needed to access the singleton like this:

let color: CGColor = UnsafePointer<MYColors>(MyModuleName.appColors!).appBackgroundColor

But that does not work either.


Solution

  • The C declaration

    extern struct MYColors * appColors;
    

    is imported to Swift as

    public var appColors: UnsafeMutablePointer<MYColors>!
    

    Dereferencing a pointer is done in Swift via the pointee property, so the Swift equivalent of the (Objective-)C code

    appColors->appBackgroundColor
    

    is

    appColors.pointee.appBackgroundColor
    

    The type of that value is Unmanaged<CGColor>! because the Swift compiler does not know how the memory of the object should be managed. In your case the caller is not responsible for releasing the object, so the final code is:

    let bgColor = appColors.pointee.appBackgroundColor.takeUnretainedValue()
    

    For more information about unmanaged references, see Unmanaged.

    Remark: If appColors and all struct members are guaranteed to be non-NULL when accessed then you can annotate them with _Nonnull in the interface:

    struct MYColors {
        CGColorRef _Nonnull appBackgroundColor;
        // ...
    };
    
    extern struct MYColors * _Nonnull appColors;
    

    The Swift compiler then imports the variables as non-optionals instead of (implicitly unwrapped) optionals.