Blogs from the Ranch

< Back to Our Blog

Callin’ U Back


Mark Dalrymple

I sometimes see the question “How do I cast a method into a function pointer?” come up during classes or on some form of social media. One problem programmers have, which asking questions, is phrasing the question too specifically. In this case, the real question is “How do I use an Objective-C method as a callback for a C library?” Casting a method to a function pointer is one possible way of solving the problem. Is it the right way?

Nope. Why not? Inside the Bracket showed that although Objective-C methods are actually C functions, they’re C functions that take two hidden arguments: self, and a selector, before they start consuming the actual set of arguments to the function. That’s what prevents you from using methods as C callbacks.

Say you’re using a library that does text manipulations, and it has a callback function that gives you a string and a piece of boolean state, and then you process the string and return it. You decide to make a function that converts the string into Swedish Chef speak:

NSString *encheferizeString (NSString *string,
                             BOOL useChicken, void *info);

You register it with the library:

SetModificationCallback (textManger, encheferizeString, infoPointer);

The library goes off and does its work, eventually feeling the need to call your function. So the library calls your function passing along the string to modify, the boolean flag, and an info pointer, (also known as user data, the context, a reference constant, or simply a rock to hide some data under). In memory, the arguments might look like this:

Callback args

But! you say. I have a method already that I want to use!

- (NSString *) encheferizeString: (NSString *) string
                      useChicken: (BOOL) useChicken;

Surely there’s some way, like getting the IMP from -methodForSelector: or something, and casting it to the callback type? Yes, you can try that:

IMP method =
  [cheferizer methodForSelector:@selector(encheferizeString:useChicken:)];
SetModificationCallback (textManager, method, cheferizer);

and get a compiler warning (you are fixing your warnings, aren’t you?)

XXCheferizer.m:36:40: warning: incompatible pointer types 
    passing 'IMP' (aka 'id (*)(__strong id, SEL, ...)') 
    to parameter of type 'TextManagerCallback' (aka 'void (*)(NSString *__strong, BOOL)')
        SetModificationCallback (textManager, method, cheferizer);

And now we’re back to “how do you cast a method into a function pointer?”

The compiler is happy to move bit patterns around arbitrarily, but it might take some coercing with casts (in this case, looks like a TextManagerCallback function pointer type) to quiet the warnings. At runtime, though, things are not happy. -encheferizeString:useChicken: is expecting its arguments in this order:

Method arguments

They don’t line up with what the C library will be passing. Here they are side-by-side:

Args together

The C library will be passing the string, the boolean flag, and the info pointer. The method, when it starts running, will pull in the value for self, but instead will get the string pointer. It’ll pull in the value for _cmd, the selector, but instead it’ll pull in the useChicken boolean. The string will actually reference the info pointer, and useChicken will have some random value. This is not good.

The Fix

How do you fix it? Pass your object, the one with the method you want to run, as the info pointer. Then use a little C function that casts the info pointer back into an object. Once you have your object pointer back, you can use it with square brackets as you normally do:

XXCheferizer *cheferizer = [[XXCheferizer alloc] initWithDialect: kSwedish];
SetModificationCallback (textManger, encheferizeString,
                         (__bridge void *)cheferizer);

NSString *encheferizeString (NSString *string, BOOL bawkbawk, void *info) {
    XXCheferizer *borkbork = (__bridge id) info;
    [borkbork encheferizeString: string  useChicken: bawkbawk];

For Reals, man

Hopefully, through the medium of muppets and poultry, I got the basic point across. Time for an actual use-case. Core Graphics has a datatype called CGShadingRef that’s used to fill in areas with custom shading. It’s like CGGradient but gives you more control at the expense of more code complexity.

To use CGShading you construct a C function that gets called repeatedly while drawing happens. You check a parameter inside that function to see how far along the drawing is and to decide which color to use. You can see a sample project, ShadyDeals. All the fun happens in XXShadyView’s implementation. I won’t mention the work necessary to get a all the CGShadingRef moving pieces configured. Well, OK I just mentioned it, but trust me it’s a bit of work to get it set up, which is just a distraction.

Here’s a shading function that will be fed, via the location parameter, a float value ranging from 0.0 to 1.0. This function uses the progress value for all the color components:

static void shadingCallback (void *info, const float *location,
                             float *results) {
    float thing = *location;

    results[0] = thing;  // Red
    results[1] = thing;  // Green
    results[2] = thing;  // Blue
    results[3] = 1.0;    // Alpha
} // shadingCallback

Resulting in a grayscale ramp:

Flat ramp

Throw in some cyclical functions to generate some groovy colors:

static void shadingCallback (void *info, const float *location,
                             float *results) {
    float thing = *location;

    results[0] = thing;
    results[1] = sin(M_PI * 2 * thing);
    results[2] = cos(M_PI * 2 * thing);
    results[3] = 1.0;

} // shadingCallback

Fancy pants ramp

Objects are great for storing bits of state that can affect behavior, as well has being a place to attach those behaviors to. Wouldn’t it be nice to use a method here, for easy access to different attributes of the view? Let’s do that.

A shader function is created with CGFunctionCreate. It takes your function pointer, your context pointer, along with some bookkeeping to describe the function’s arguments and return values:

CGFunctionCallbacks <strong>callback</strong> = { 0, <strong>shadingCallback</strong>, NULL };
CGFunctionRef <strong>shaderFunction</strong> =
    CGFunctionCreate (<strong>(__bridge void *)self</strong>,  // context / info
                      1,     // number of inputs for domain
                      4,     // number of outputs for range

The interesting parts are in bold. The C callback function (shadingCallback) is first put into a structure (which includes a version number, zero, and a callback for releasing the info pointer. We don’t need this so just pass NULL). This structure is then used to create the shader function object. shaderFunction encapsulates the callback, as well as the info pointer. The info pointer is self, which is the UIView that’s drawing itself on the screen. The C callbacks you saw earlier just ignored the info pointer.

Methodical Treatment

Here’s a method to do the calculations:

- (void) evalShadingAtLocation: (float) location
              returningResults: (float *) results {

    if (self.fancyColors) {
        results[0] = location;
        results[1] = sin(M_PI * 2 * location);
        results[2] = cos(M_PI * 2 * location);
        results[3] = 1.0;
    } else {
        results[0] = location;
        results[1] = location;
        results[2] = location;
        results[3] = 1.0;

} // evalShadingAtLocation

It’s just copy/paste of the C-callback code, but puts both the gray-ramp and the psychedelia in one place, using a property to decide which one to use. Time to get it called. As a reminder, we can’t just cram it into the CGFunctionCallbacks structure because of the immiscibility of the function arguments:

Shading args

What should shadingCallback look like now?

static void shadingCallback (void *info, const float *in, float *out) {
    XXShadyView *view = (__bridge XXShadyView *)info;

    [view evalShadingAtLocation: *in
               returningResults: out];

} // shadingCallback

Cast info to the proper type and then message your view (via the info pointer) as usual.

But, but, Your Wrong!!1!

Sometimes after explaining the problem with C-function vs Objective-C method argument layout, I get someone who provides a Startling Counterexample™ that renders my argument moot and shames my family unto the seventh generation. I try it, and sure enough, it seems to run correctly. These counterexamples typically follow the same pattern. There’s a When that happens, it’s usually a C callback function that passes the info pointer as the first argument:

void callbackFunction (void *info, ... additional arguments);

The corresponding method implementation of the method never does anything useful:

- (void) neenerNeener: (int) additionalArguments {
    // oh look, the arguments are not used in here.
    NSLog (@"ha ha");

“It prints out HA HA and doesn’t crash!” In a sense, this could actually “work.” How? The info pointer, which would be pointing to an object, comes first in the list of arguments, like this:

No ur wrong

But notice that the method doesn’t use any arguments, so the incorrect location of the arguments in memory doesn’t affect anything. There would be definitely be bugs if the method tried to do something with any arguments that were passed in. The method goes to grab its first parameter, actually picking up the second parameter provided by the C library. If you’re lucky, the types will be completely incompatible (passing a float as an argument, which then gets treated as an NSString pointer) and crash spectacularly at runtime. If you’re unlucky, you’ll just corrupt some unsuspecting chunk of data that gets saved permanently.

Wrap Up

What’s the ultimate take-away? By being aware of how things work under the hood, specifically how methods are passed their arguments, you can understand why the compiler is giving you grief when you try to stuff an IMP into an arbitrary C function pointer, and how to work around it.


Mark Dalrymple

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project