calling u back

cocoa框架基于C语言除了良好的运行性能外还有个重要的优点就是强大丰富的C库支持。ios开发中C库和oc的互相调用对于开发者来说也是不小的挑战,国外网站看到一篇文章,翻译给大家,个人进行了精简:

我有时看到这样的问题:“我怎么把一个oc方法转化为方法指针,是在编译的时候转化还是本来就是cocoa框架的一种形式”。一个程序员错误的这样问了,就会让这个问题变得狭隘。其实这个问题应该这么问:“ How do I use an Objective-C method as a callback for a C library?” 意思就是怎么用c库回调oc的消息(方法)。把oc方法转化为c方法指针可能是解决这个问题的一种方法,真的这样吗? 不是,为什么?Inside the Bracket 这片文章告诉我们OC的方法其实也是C函数,只不过是在调用实参之前就封装了两个参数,self和selector. 这正是我们不能直接把oc方法作为c库回调的原因。 比例你用一个C库进行文本处理,它有一个回调方法并且给定了string和一个bool变量,你需要写个函数来转化字符串,简单写法:

1
2
3
4
NSString *encheferizeString (NSString *string,
BOOL useChicken, void *info);
在C库中进行注册:
SetModificationCallback (textManger, encheferizeString, infoPointer);

C库运行工作的时候最终需要调用你写的encheferizeString方法,所以C库调用你的方法和3个参数:string,the boolean flag , an info pointer(通常这三个参数被理解为用户数据,内容,一个固定指针或者只是隐藏一些数据)在内存中,参数看起来这么排列

不过,你现在要说 ,我已经有了一个方法了,我要使用已有的方法!

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

当然你可以用IMP 从-methodForSelector: 进行转化,然后再转化为C库可用的回调方法,你可以试一下:

1
2
3
IMP method =
[cheferizer methodForSelector:@selector(encheferizeString:useChicken:)];

SetModificationCallback (textManager, method, cheferizer);

然后你编译器就会有警告:

1
2
3
4
5
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);

我们回到“怎么把OC方法转化为C回调方法?”这个问题上。 编译器回非常高兴武断的去执行这段代码,而不会顾及这段警告。但我们需要强制去除这些警告,否则在运行时环境里,有些事情却不那么令人兴奋了。-encheferizeString:useChicken: 的参数排列顺序是这样的:

这和c的执行顺序不一样,下面是对比图

C库传递的参数是 the string,the boolean flag and the info pointer.而自己已经写好的oc方法运行时会传递self 参数,但实际上应该传的 string pointer.依次类推,后面参数全都混乱了

解决方法

你是怎么解决的呢? 传递一个包含你要运行的oc方法的object,作为info pointer. 然后 使用一个轻量级的c方法来获取这个object,准备就绪后,你可以像第一个例子那样按照正常的c方法回调使用了。简单代码如下:

1
2
3
4
5
6
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]}

####一个真实的例子

Core Graphics里面有个数据类型CGShadingRef大家应该都用过,用来填充图形阴影的。CGShadingRef很像CGGradient,但却给你更多的编程空间。 想要使用CGShading 你必须构建一个C方法来供来回调用,当然是在绘图的时候。你可以用参数来控制画多长或者使用哪种颜色。你可以看一个简单的例子- ShadyDeals.(看不看无关紧要)我不会详细的介绍CGShading。 这里是构建阴影的方法。通过参数location(float类型,值范围 0.0-1.0)来实现动态改变颜色组成。

1
2
3
4
5
6
7
8
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

效果如下:

A shader function 是用CGFunctionCreate创建的。它封装了方法指针,你的内容指针,还有一些参数

1
2
3
4
5
6
7
8
CGFunctionCallbacks callback = { 0, shadingCallback, NULL };
CGFunctionRef shaderFunction =
CGFunctionCreate ((__bridge void *)self, // context / info
1, // number of inputs for domain
domain,
4, // number of outputs for range
range,
&callback);

代码看起来非常有趣,c回调方法(shadingCallback)首先被封装在一个类似结构体里(这个结构体第一个参数是version,默认是0,第三个为用来释放info指针的回调方法,我们不需要,所以为NULL)。这个结构体用来创建 shader function object,shaderFunction包含了callback,相当于info pointer。在这个例子里info pointer 是self自己,也就是UIView,把自己画在屏幕上,上面假设的例子只不过是把info pointer省略了。

原文链接

http://blog.bignerdranch.com/3727-callin-u-back/

相关阅读

objective-c 和C++混编:

http://blog.csdn.net/zhouhuiah/article/details/6426158