我有一个C ++库,可以进行包括网络在内的异步工作。它有一个特定于Darwin的后端,它使用Grand Central Dispatch的C API将工作委托给其他线程。现在我想通过一个薄层的ObjC ++从一个用Swift编写的新iOS应用程序中使用该库。
我在OS X 10.10上使用Xcode 6.3.2。
在这个最小的例子中,我重新创建了上述架构。问题是启动异步操作的ObjC类实例在某种程度上是“#34;破坏"当操作通过std :: function回调返回时。如果std :: function声明为[&]
而不是[=]
,则只发生 。我不能使用后者,因为它不支持"真正的" C ++代码。
调用ObjC ++的Swift代码如下所示:
class MyViewController : UIViewController {
var test = ObjectiveTest()
override func viewDidAppear(animated: Bool) {
test.testAsyncWork("some data", withHandler: { (result: Int32) -> Void in
println("Work result: \(result)")
})
}
}
如果我注释掉该代码,则显示视图控制器并保持可见,因此它不应该杀死ObjectiveTest实例。这就是ObjC ++胶水层:
@interface ObjectiveTest () {
__block Test *test;
__block int _a;
}
@end
@implementation ObjectiveTest
- (id)init
{
self = [super init];
if (self) {
test = new Test();
_a = 42;
if (!test)
self = nil;
}
return self;
}
- (void)deinit
{
delete test;
}
- (void)testAsyncWork:(NSString *)someData withHandler:(WorkBlock)handler
{
_a++;
NSLog(@"_a = %i", _a); // valid
NSLog(@"handler = %@", handler); // valid
test->testAsyncWork(someData.UTF8String, [&](int result) {
_a++;
NSLog(@"result = %i", result); // Expected: "result = 666" - valid
NSLog(@"_a = %i", _a); // Expected: "_a = 44" - invalid
NSLog(@"handler = %@", handler); // invalid, crashes here
});
}
@end
最后,执行"工作":
的C ++方法void Test::testAsyncWork(std::string someData, std::function<Handler> handler) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
printf("Data received: %s, calling handler on other thread\n", someData.c_str());
sleep(1); // This is hard work!
dispatch_async(dispatch_get_main_queue(), ^{
printf("Hello, main thread here, calling back now!\n");
handler(666); // into ObjectiveTest
});
});
}
正如我所说,如果我将[=]
用于std :: function,这不会中断。
尽管使用_a
声明,但ObjectiveTest的ivar __block
似乎在回调函数中具有随机值。尝试访问(打印/调用)调用回Swift代码的块handler
时,程序崩溃。 Xcode显示如下:
handler WorkBlock & error: summary string parsing error 0xbffa50cc
&handler __block_literal_generic * 0x7c000000 0x0ae08500
__isa void * NULL 0x00000000
__flags int 0 0
__reserved int 2080876705 2080876705
__FuncPtr void (*)(int) 0x7c000000 0x7c000000
__descriptor __block_descriptor * 0x7c085b10 0x7c085b10
由此,我得到的印象是ObjectiveTest实例在进程的某个地方中断,但由于它保存在MyViewController中,我不知道这是怎么发生的。我可能会错过其他什么吗?
答案 0 :(得分:3)
[&]
通过引用捕获变量。如果原始变量在任务完成之前到期,则捕获的引用将悬空。
由于异步调用可能是异步完成 1 ,所以基本上保证了悬空引用。
进行异步调用时,您几乎总是希望按值捕获。您甚至可能希望列出正在捕获显式的内容,以便了解您要引入的依赖项。 (多线程代码很难,没有隐藏/隐式依赖)
[&]
捕获的唯一有效用途是当您创建一个&#34;访问者&#34;键入对象以传递给将使用lambda的函数,然后在它返回之前丢弃它及其所有副本。除此之外的任何事情你都应该通过价值来捕捉,或者非常仔细地挑选你通过引用捕获的内容,并证明生命周期问题都已被涵盖。
1 可能希望[&]
的异步方法示例是在UI泵线程上运行&#34;&#34;异步调用,您希望工作线程停止进度,直到结果从UI线程返回。在那里,您[&]
,并在离开当前范围之前阻止返回的std::future
(或等效的)。