如何在C结构中保存Obj-C块?

时间:2017-03-31 06:11:28

标签: ios objective-c c struct completionhandler

我想从我的ObjC调用一个C函数。我在C结构中传递一个C函数引用(在ObjC中定义),以便C可以调用该函数。我还想将完成块的引用传递给C,这样当我得到一个回调时,我可以调用该完成块。但我不知道如何实现这一点。根据我尝试的不同类型转换,我得到了不同的错误。

//Abc.m

void myCallback(MyData *data) 
{
  //I get the control here!
  //((__bridge void *)(data->completionBlock))([NSString stringWithCString:data->json]); //how to call the completion block?
}

- (void)myMethod:(NSString)input
             completion:(void (^)(NSString * _Nullable response))completionBlock 
{
    MyData *data = malloc(sizeof(MyData));
    data->myCallback = myCallback;
    data->completionBlock = (__bridge void *)(completionBlock);//is this correct?
    cFunction(data);
}


//Xyz.c

typedef struct
{
  char *json;
  void (*myCallback)(void *response);
  void *completionBlock;
} MyData;

void cFunction(MyData *data)
{
  data->json = "some response";
  (data->myCallback)(data);
}

2 个答案:

答案 0 :(得分:1)

有两个问题需要考虑:

  1. 投射:您需要投射到适当的类型,例如您注释掉的(__bridge void *)(data->completionBlock)不会返回块类型,因此编译器将拒绝该调用。
  2. 所有权:块只是Objective-C中的对象,由ARC管理。在C块中手动管理。您必须确保传递到C结构的块不会被ARC释放,并且在使用它之后必须确保它已被释放。
  3. 按照您的代码设计,我们首先定义一个类型,以简化操作:

    typedef void (^CompletionBlock)(NSString * _Nullable response);
    

    这样你的myMethod函数就像以前一样开始:

    - (void)myMethod:(NSString*)input
          completion:(CompletionBlock)completionBlock
    {
       MyData *data = malloc(sizeof(MyData));
       data->myCallback = myCallback;
    

    现在,您必须将块存储到结构中,同时确保ARC不会在Objective-C端释放它。为此,您使用__bridge_retain返回对块的保留引用,您的代码将负责平衡保留。这可以在您的C代码中完成,也可以将所有权转移回ARC并让它来处理它。所以myMethod的剩余部分是:

       data->completionBlock = (__bridge_retained void *)(completionBlock);
       cFunction(data);
    }
    

    现在您的cFunction只调用了您的myCallBack功能,因此在这种情况下无需更改任何内容。

    现在转到myCallBack,首先我们修复了类型不匹配问题,并将其定义为void *,然后恢复MyData *

    void myCallback(void *response)
    {
       MyData *data = response;
    

    现在我们需要恢复阻止。我们可以将它转换为块类型,但这样我们就可以在使用它之后将它释放出来(使用Block_release());但是我们可以使用__bridge_transfer将所有权交还给ARC,以便管理它:

       CompletionBlock completionBlock = (__bridge_transfer CompletionBlock)data->completionBlock;
    

    现在我们将字符串输出并将其转换为NSString

       NSString *result = [NSString stringWithCString:data->json encoding:NSUTF8StringEncoding];
    

    然后释放malloc'ed包装器:

       free(data);
    

    最后我们称之为阻止:

       completionBlock(result);
    }
    

    以上是你的设计,但是没有必要让你的C函数调用Objective-C文件中的另一个C函数来调用块 - 块是C语言特性并且在.c文件中受支持铛。您可以将data->completionBlock转换为块类型,调用它,然后使用Block_release()释放块。

    此外,由于块是C类型,您可以使用块类型键入struct field completionBlock并删除大量的强制转换(但这些情况在运行时无需任何成本)。

    HTH

答案 1 :(得分:0)

__bridge表示使用c样式指针,其行为如assign__unsafe_unretained

您使用它意味着data->completionBlock只指向completionBlock而不保留,因此您可能会在completionBlock被释放后崩溃。

如果您想使用ARC访问struct中的block或objective-c对象,则必须使用__unsafe_unretained声明这些对象并自行管理它们。

typedef struct {
char *json;
__unsafe_unretained NSString *name;
__unsafe_unretained CompletionBlock block;
} SampleStruct;