【问题标题】:Mixing C functions in an Objective-C class在 Objective-C 类中混合 C 函数
【发布时间】:2009-04-29 11:44:42
【问题描述】:

我正在编写一个 Objective-C 类,但它使用了一个用 C 编写的 API。这通常很好,因为将 C 调用与 Objective-C 调用混合会导致一些问题。

但是其中一个 API 调用需要回调方法(示例):

success = CFHostSetClient(host, MyCFHostClientCallBack, &context);

MyCFHostClientCallBack 是一个 C 函数,定义如下:

static void MyCFHostClientCallBack(CFHostRef host, CFHostInfoType typeInfo, const CFStreamError *error, void *info);
  1. 可以/如何调用 Objective-C 方法来代替它?
  2. 我可以/应该将 C 函数与我的 Objective-C 调用混合使用吗?
  3. 如何将 C 函数与 Objective-C 方法混合使用?

【问题讨论】:

    标签: c objective-c cocoa callback


    【解决方案1】:

    混合 C 和 Objective-C 方法和函数是可能的,这是一个在 iPhone 应用程序中使用 SQLite API 的简单示例:(course site)

    下载Zip file (09_MySQLiteTableView.zip)

    C 函数需要在 Objective-C (.m) 文件中的 @implementation 之外声明。

    int MyCFunction(int num, void *data)
    {
         //code here...
    }
    
    @implementation
    
    - (void)MyObjectiveCMethod:(int)number withData:(NSData *)data
    {
          //code here
    }
    
    @end
    

    因为 C 函数在 @implementation 之外,它不能调用类似的方法

    [self doSomething]
    

    并且无法访问 ivars。

    只要回调函数采用userInfocontext 类型参数(通常为void* 类型),就可以解决此问题。这可用于将任何 Objective-C 对象发送到 C 函数。

    就像在sample code 中一样,这可以通过正常的Objective-C 操作进行操作。

    另外请阅读这个答案:Mixing C functions in an Objective-C class

    【讨论】:

    • C 函数不需要在@implementation之外。
    • @Bavarious:编译器可以让你摆脱@implementation 中的函数,但它仍然不是@implementation 的一部分,因为函数不能属于一个类。就风格而言,我把它放在@implementation 之外,并建议其他人也这样做,以明确这一点。
    • 实际上,@implementation 块中的 C 函数具有能够直接访问私有和受保护的 ivars 的独特属性。因此,根据我自己的经验,将“属于”一个类的 C 函数放在相应的实现中已成为一种强烈的习惯用法。我建议更新您的答案。
    • 澄清一下,如果他们被授予访问类实例的权限,他们将可以访问私有 ivars。例如。 int MyCFunction(MyClass* self, int num, void *data)。这与编译 -MyObjectiveCMethod:withData: objc-method 时的幕后情况非常相似。
    • @JoshRosen:很有趣,但我认为这是一个实现细节,而不是明确定义并保证存在的语言特性。
    【解决方案2】:

    要从 C 回调中调用 Objective-C 代码,我会使用类似:

    void * refToSelf;
    int cCallback()
    {
        [refToSelf someMethod:someArg];
    }
    
    @implementation SomeClass
    - (id) init
    {
         self = [super init];
         refToSelf = self;
    }
    - (void) someMethod:(int) someArg
    {
    }
    

    【讨论】:

    • 只有一个补充:许多回调允许指定“用户数据”;在这种情况下,对象指针可以用作用户数据(您不再需要全局变量 refToSelf)。可能是您的回调函数支持信息指针中的“用户数据”。
    • 在使用上述代码启用 ARC 的情况下,我收到运行时内存泄漏警告。 stackoverflow.com/questions/11840943/…
    • 这是错误的实现。它假设您只有一个 ComeClass 实例。无效 *refToSelf;是一个静态/全局变量。每次您 [[SomeClass alloc] init] 时,您的 refToSelf 都会被最后创建的实例覆盖。 cCallback 不会调用正确的 SomeClass,而是始终调用最后一个创建的。此外 - 当最后创建的 SomeClass 被释放时(至少在上面的代码中)它会崩溃,因为您没有在 SomeClass 的释放时清除 refToSelf。
    • 补充@ashcatch 所说的:如果您的回调被赋予void *userData,请确保使用(__bridge id)(userData) 将其转换为指向您的Objective-C 类实例的指针,以避免警告和泄漏。
    • 如果某个类是单例的,这没关系。但它不是单身,同意@MottiShneor 的回答。
    【解决方案3】:

    我可以/如何调用一个 Objective-C 方法来代替它?

    你不能。

    我可以/应该将 C 函数与我的 Objective-C 调用混合吗?

    是的。编写一个 C 函数并将其用作 CF 函数的回调。

    如何将 C 函数与 Objective-C 方法混合使用?

    您可以在上下文结构中将self 设置为info 指针。这将被传递给回调。然后,在回调中,将info 指针转换回id

    MyClass *self = (id)info;
    

    然后您可以发送self 消息。但是,您仍然不能直接访问实例变量,因为 C 函数位于 @implementation 部分之外。您必须使它们成为属性。您可以使用class extension 来完成此操作。 (与该文档所说的相反,您不会在 @implementation 中声明扩展名,而是在与它相同的文件中声明扩展名,通常就在它的上方。)

    【讨论】:

      【解决方案4】:

      在这种情况下,我总是发现在 C API 之上创建一个 Obj-C 包装器很有帮助。使用 C 函数实现您需要的东西,并在其上构建一个(或两个)Objective-C 类,这样外界就会看到。例如,在这样的回调的情况下,您可能会创建一个 C 函数来调用其他对象上的 Obj-C 委托方法。

      【讨论】:

        【解决方案5】:

        .m调用.c内部的函数:

        • CrifanLib.h
        #ifndef CrifanLib_h
        #define CrifanLib_h
        
        #include <stdio.h>
        
        void fileModeToStr(mode_t mode, char * modeStrBuf);
        
        #endif /* CrifanLib_h */
        
        • CrifanLib.c
        #include "CrifanLib.h"
        
        #include <stdbool.h>
        
        void fileModeToStr(mode_t mode, char * modeStrBuf) {
            // buf must have at least 10 bytes
            const char chars[] = "rwxrwxrwx";
            for (size_t i = 0; i < 9; i++) {
        //        buf[i] = (mode & (1 << (8-i))) ? chars[i] : '-';
                bool hasSetCurBit = mode & (1 << (8-i));
                modeStrBuf[i] = hasSetCurBit ? chars[i] : '-';
            }
            modeStrBuf[9] = '\0';
        }
        

        Objective-C.m调用:

        #include “CrifanLib.h"
        
        @interface JailbreakDetectionViewController ()
        
        @end
        
        @implementation JailbreakDetectionViewController
        …
        
        char* statToStr(struct stat* statInfo){
            char stModeStr[10];
            fileModeToStr(statInfo->st_mode, stModeStr);
        ...
        }
        
        ...
        

        完成。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-07-19
          • 1970-01-01
          相关资源
          最近更新 更多