如何将 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 结构中?的主要内容,如果未能解决你的问题,请参考以下文章