Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
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:
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)')
[-Wincompatible-pointer-types]
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:
They don’t line up with what the C library will be passing. Here they are side-by-side:
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.
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];
}
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:
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
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
domain,
4, // number of outputs for range
range,
<strong>&callback</strong>);
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.
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:
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.
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:
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.
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.
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...