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

Posted

技术标签:

【中文标题】如何将 Obj-C 块保存在 C 结构中?【英文标题】:How to save Obj-C block in a C struct? 【发布时间】:2017-03-31 06:11:27 【问题描述】:

我想从我的 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);

【问题讨论】:

【参考方案1】:

有两个问题需要考虑:

    强制转换:您需要强制转换为适当的类型,例如您注释掉的 (__bridge void *)(data->completionBlock) 不会给您返回块类型,因此编译器将拒绝调用。 所有权:块只是 Objective-C 中的对象,由 ARC 为您管理。在 C 中,块是手动管理的。您必须确保传递给您的 C 结构的块不会被 ARC 释放,并且在使用它之后,您必须确保它被释放。

按照您的代码设计,让我们首先定义一个类型以使事情变得更容易:

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 的包装器:

   free(data);

最后我们调用块:

   completionBlock(result);

以上内容遵循您的设计,但无需让您的 C 函数调用 Objective-C 文件中的另一个 C 函数来调用块 - 块是 C 语言功能,并在 Clang 的.c 文件中支持。您可以将data->completionBlock 转换为块类型,调用它,然后使用Block_release() 释放块。

此外,块是 C 类型,您可以使用块类型键入结构字段 completionBlock 并删除大量强制转换(但这些情况在运行时不需要任何成本)。

HTH

【讨论】:

【参考方案2】:

__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;

【讨论】:

如你所见,结构体是用C定义的;所以 CompletionBlock 是未知类型名称。 Completion 是像typedef void(^CompletionBlock)(void); 这样的块声明 如果你想访问纯c文件中的块,只需声明void *,但你需要自己管理生活。 可以看到我已经在使用void *了。但是当我从 myCallback 方法调用它时出现错误的访问错误 你必须自己管理completeBlock生活。很明显finish方法'-myMethod:completion:', completeBlock已经被释放了。

以上是关于如何将 Obj-C 块保存在 C 结构中?的主要内容,如果未能解决你的问题,请参考以下文章

如何将每次迭代的输出保存到结构中

如何将拆分的字符串保存在C结构中包含的字符数组中?

c语言中如何修改储存在文件的结构体内容中。小文件

如何在 iPad 上使用 JavaScript/Obj-C 将图像保存到照片库?

如何将数据存入到一个结构体中

c语言中如何将按结构体中的某个元素大小,将结构体排序输出