Interop Objective-C Objects In C Using ARC

Introduction

Using C functions from Objective-C is very easy but going the other way isn’t so easy. Especially with ARC which can destroy the object out from under you because C code is outside of ARC. With ARC Objective-C objects are no longer allowed in C structs for this very reason.

Here is a situation I ran into where I needed to use Objective-C objects from within C. At work we have a library that was ported to iOS. The library needed to use a part of the OS that is only accessible by Objective-C API calls. Since this library is multi platform we have an OS independent API that we have OS specific implementations. So our C library needed to use Objective-C objects to implement the OS specific functionality required. Porting the library from C to Objective-C isn’t an option.

Wrapping Objective-C functions in C

The solution is to wrap Objective-C objects in C functions. The C functions will still be in a .m file and can still be compiled directly into a C app (or library). This just works on OS X (cross compiling for iOS or not) without needing to do anything special.

Simple Example

This wraps NSLog in C functions so it can be called from C.

logger.h

#ifndef __LOGGER_H__
#define __LOGGER_H__

void do_objc_log(const char *msg);
void do_objc_log_fmt(const char *fmt, ...);

#endif /* __LOGGER_H__ */

logger.c

#include "logger.h"

#import <Foundation/Foundation.h>

void do_objc_log(const char *msg)
{
    if (msg == NULL || *msg == '\0')
    	return;
    NSLog(@"%@", [NSString stringWithUTF8String:msg]);
}

void do_objc_log_fmt(const char *fmt, ...)
{
    va_list args;

    if (fmt == NULL)
        return;

    va_start(args, fmt);
    NSLogv([NSString stringWithUTF8String:fmt], args);
    va_end(args);
}

This is a very simple example that only wraps a single function and not complex objects.

Wrapping Objective-C objects in C

Lets look at how we can create an object in C before we get into putting Objective-C objects in them. Basically you have struct that holds data and functions that take the struct to operate on the data.

In this implementation we’d need to put the Objective-C object in the struct. However, ARC doesn’t allow this because it can’t track objects in C structs. That’s not entirely true, we can technically do this by using __unsafe_unretained but this is a bad idea. As the attribute states this is unsafe because the struct won’t retain the object. ARC will destroy the object once it thinks it’s no longer in use. ARC doesn’t know that the object is “owned” at this point by the struct.

The solution is to use CFTypeRef as the type in the struct and cast the object to and from this type using the following attributes:

__bridge_retained
Cast and transfer ownership from Objective C to C. This will increment the reference count keeping the object from being destroyed. ARC knows the object is in use. This needs to be paired with __bridge_transfer when it’s no longer needed otherwise the object will never be destroyed.
__bridge
Cast and doesn’t transfer ownership. Doesn’t change the reference count. This lets you take the CFTypeRef and use it as the Objective-C object it is. Without this the object type isn’t known so you can’t call its functions.
__bridge_transfer
Cast and transfer ownership from C to Objective C. This will decrement the reference count allowing ARC to destroy the object when necessary.

The __bridge_* casts need to be used with care. They are changing the reference count so if they’re not used correctly you can either have a memory leak or worse an object that get’s destroyed while still being used.

If you look at the definition of CFTypeRef you’ll see it’s just a void pointer. You have to use the type an not a void pointer otherwise you’ll get a compilation warning when using the __bridge* attributes in a cast.

You don’t necessarily need to put the CFTypeRef’ed object in a struct. You could have you’re functions take the CFTypeRef directly but I’d recommend against that because you’ll lose the type safety that you get from C. Another way to handle this is to typedef CFTypeRef and use that in the C functions. This makes use clearer. This is fine but I like the idea of using a struct because you can have more data than just the object to essentially extend the object.

Object Wrapping Example

Object

The object just takes an NSInteger as a starting parameter and has two functions to add and subtract from the stored value.

mather.h

#ifndef __MATHER_H__
#define __MATHER_H__

#import <Foundation/Foundation.h>

@interface Mather : NSObject

@property (readonly)NSInteger currentVal;

+ (id)mather:(NSInteger)start;

- (id)init:(NSInteger)start;
- (void)add:(NSInteger)val;
- (void)sub:(NSInteger)val;

@end

#endif /* __MATHER_H__ */

The object implementation.

mather.m

#import "mather.h"

@implementation Mather

+ (id)mather:(NSInteger)start
{
    return [[Mather alloc] init:start];
}

- (id)init:(NSInteger)start
{
    self = [super init];
    if (self == nil)
    	return nil;

    _currentVal = start;
    return self;
}

- (void)add:(NSInteger)val
{
    _currentVal += val;
}

- (void)sub:(NSInteger)val
{
    _currentVal -= val;
}

@end

C Wrapper

The wrapper puts the Objective-C object into a C struct. There are create and destroy functions which handle creation and the ownership bridging. This C “object” also hides the implementation of the struct so it an be changed without breaking ABI compatibility. It also prevents accidental manipulation of the data.

The wrapper handles creating, taking, using, and destroying the Objective-C object. It also implements two functions that does not uses a retained object. Instead it creates and uses the object without retaining it. The object is created, used, the value is returned and since the object is no longer needed ARC handles destroying it.

wrapper.h

#ifndef __WRAPPER_H__
#define __WRAPPER_H__

struct wadder;
typedef struct wadder wadder_t;

wadder_t *wa_create(int start);
void wa_destroy(wadder_t *wa);

int wa_started(wadder_t *wa);

void wa_add(wadder_t *wa, int val);
void wa_sub(wadder_t *wa, int val);
int wa_val(wadder_t *wa);

int wa_adds(int val1, int val2);
int wa_subs(int val1, int val2);

#endif /* __WRAPPER_H__ */

wrapper.m

#include <stdlib.h>

#import "mather.h"
#include "wrapper.h"

struct wadder {
    CFTypeRef ma;
    int       start;
};

wadder_t *wa_create(int start)
{
    Mather   *ma;
    wadder_t *wa;

    ma = [Mather mather:start];
    if (ma == nil)
        return NULL;
    
    wa        = malloc(sizeof(*wa));
    wa->start = start;
    wa->ma    = (__bridge_retained CFTypeRef)ma;

    return wa;
}

void wa_destroy(wadder_t *wa)
{
    Mather *ma;

    if (wa == NULL)
        return;

    ma = (__bridge_transfer Mather *)wa->ma;
    ma = nil;

    free(wa);
}

void wa_add(wadder_t *wa, int val)
{
    Mather *ma;

    if (wa == NULL)
        return;

    ma = (__bridge Mather *)wa->ma;
    [ma add:val];
}

void wa_sub(wadder_t *wa, int val)
{
    Mather *ma;

    if (wa == NULL)
        return;

    ma = (__bridge Mather *)wa->ma;
    [ma sub:val];
}

int wa_val(wadder_t *wa)
{
    Mather *ma;

    if (wa == NULL)
        return 0;

    ma = (__bridge Mather *)wa->ma;
    return [ma currentVal];
}

int wa_adds(int val1, int val2)
{
    Mather *ma;

    ma = [Mather mather:val1];
    [ma add:val2];

    return [ma currentVal];
}

int wa_subs(int val1, int val2)
{
    Mather *ma;

    ma = [Mather mather:val1];
    [ma sub:val2];

    return [ma currentVal];
}

int wa_started(wadder_t *wa)
{
    return wa->start;
}

Putting it All Together

The C file will use the C functions exposed by the header files.

main.c

#include <stdio.h>
#include "wrapper.h"
#include "logger.h"

int main(int argc, char **argv)
{
    wadder_t *cadder;

    do_objc_log("Starting example app");

    cadder = wa_create(7);
    do_objc_log_fmt("Start val: %d", wa_val(cadder));
    wa_add(cadder, 7);
    do_objc_log_fmt("Added 7: %d", wa_val(cadder));
    wa_sub(cadder, 4);
    do_objc_log_fmt("subtracted 4: %d", wa_val(cadder));
    do_objc_log_fmt("Started: %d. Current: %d", wa_started(cadder), wa_val(cadder));
    wa_destroy(cadder);

    printf("%s %s %s\n", "Random", "printf", "here");

    do_objc_log_fmt("no object add 1+2: %d", wa_adds(1, 2));
    do_objc_log_fmt("no object sub 1-2: %d", wa_subs(1, 2));

    do_objc_log("Done");
    return 0;
}

Output

$ ./bin/bridge 
2015-12-18 18:06:56.657 bridge[49240:1478448] Starting example app
2015-12-18 18:06:56.659 bridge[49240:1478448] Start val: 7
2015-12-18 18:06:56.659 bridge[49240:1478448] Added 7: 14
2015-12-18 18:06:56.659 bridge[49240:1478448] subtracted 4: 10
2015-12-18 18:06:56.659 bridge[49240:1478448] Started: 7. Current: 10
Random printf here
2015-12-18 18:06:56.659 bridge[49240:1478448] no object add 1+2: 3
2015-12-18 18:06:56.659 bridge[49240:1478448] no object sub 1-2: -1
2015-12-18 18:06:56.659 bridge[49240:1478448] Done

Building Everything

CmakeLists.txt

cmake_minimum_required (VERSION 3.0)
project (bridge C)

# Put binary in a bin dir.
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)

set (SOURCES
    main.c
    wrapper.m
    mather.m
    logger.m
)

# Apple frameworks and libs needed.
find_library (FOUNDATION Foundation)
find_library (OBJC objc)

add_executable (${PROJECT_NAME} ${SOURCES})
target_link_libraries (${PROJECT_NAME} ${FOUNDATION} ${OBJC})
# Enable ARC
set_target_properties (${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fobjc-arc")

Summary

To use Objective-C objects in C create a .m file with only C functions (and an accompanying .h file). Use __bridge_retained, __bridge, __bridge_transfer on the Objective-C object from within the C functions.