我需要使用MKMapSnapshotter API拍摄mapView的快照,并对它返回的图像进行一些操作,以及获取和写入核心数据。由于这是一项工作,我将它包装在NSOperation对象中,该对象在背景NSOperationQueue上执行。
MKSnapshotter api在主线程上运行完成块:
- (void)startWithCompletionHandler:(MKMapSnapshotCompletionHandler)completionHandler
或者它允许您指定要使用的调度队列:
- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MKMapSnapshotCompletionHandler)completionHandler
...所以当它在setSnapshot:
中返回时,我总是在一个不同于我应该的线程上,这可能会使我的核心数据管理对象上下文变得困难。有没有办法引用我称之为快照的线程?或者我需要在这里重新考虑我的设计吗?
- (instancetype) initWithManagedObject:(NSManagedObject *)managedObject {
// store object id for later use
_objectID = managedObject.objectID;
_mainThreadContext = managedObject.managedObjectContext;
...
}
- (void)start {
// do stuff with core data on the background moc
self.backgroundContext = [[NSManagedObjectContext alloc] init];
[self.backgroundContext setParentContext:self.parentContext];
NSError *objectIDRetrievalError = nil;
self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
...
// take the map snapshot
...
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
...
self.snapshot = snapshot;
}];
}
- (void)setSnapshot:(MKMapSnapshot *)snapshot {
// HELP, I'm on the wrong thread!
// do some expensive image stuff
...
// do some more stuff with core data on the background moc
[self.objectInBackgroundContext doStuff];
...
[self.backgroundContext save:&error];
...
[self finish];
}
答案 0 :(得分:0)
如果您只想在主线程上调用setSnapshot
,可以通过将操作提交到与主线程关联的队列来完成此操作:[NSOperationQueue mainQueue]
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{self.snapshot = snapshot;}];
[[NSOperationQueue mainQueue] addOperation:op]
或GCD
dispatch_async(dispatch_get_main_queue(), ^{
self.snapshot = snapshot;
});
更新v2
您可能会发现有用的coredata的内置并发支持。您可以从Concurrency Support for Managed Object Contexts
开始- (void)start {
// do stuff with core data on the background moc
self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.backgroundContext setParentContext:self.parentContext];
__block NSError *objectIDRetrievalError = nil;
[self.backgroundContext performBlockAndWait:^{
self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
}];
...
// take the map snapshot
...
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
...
self.snapshot = snapshot;
}];
}
- (void)setSnapshot:(MKMapSnapshot *)snapshot {
// HELP, I'm on the wrong thread!
// do some expensive image stuff
...
[self.backgroundContext performBlockAndWait:^{
// do some more stuff with core data on the background moc
[self.objectInBackgroundContext doStuff];
...
[self.backgroundContext save:&error];
}];
...
[self finish];
}
更新v3
由于@CouchDeveloper指出当前的操作实现不太正确。根据{{3}}和Concurrency Programming Guide引用,start
方法应该
main
已实施
main
start
内运行操作
然后,结合所有的东西:
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
// Run main in concurrent queue
self.dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(self.dispatchQueue, ^{
[self main]
});
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
// it runs inside self.dispatchQueue queue
// create moc with its own private queue
self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.backgroundContext setParentContext:self.parentContext];
__block NSError *objectIDRetrievalError = nil;
[self.backgroundContext performBlockAndWait:^{
self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
}];
...
// take the map snapshot
...
[snapshotter startWithQueue:self.dispatchQueue // make the completion handler run in self.dispatchQueue
completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
...
self.snapshot = snapshot;
}];
}
- (void)setSnapshot:(MKMapSnapshot *)snapshot {
// will be performed in self.dispatchQueue too
// do some more stuff with core data on the background moc
[self.backgroundContext performBlockAndWait:^{
[self.objectInBackgroundContext doStuff];
...
[self.backgroundContext save:&error];
}];
...
[self finish];
}
答案 1 :(得分:0)
您可以使用调度信号量等待完成处理程序运行,然后在原始上下文中完成后分配snapshot
属性。在操作中阻塞线程并不好,但考虑到API的限制,它可能是最好的选择。
- (void)start {
// do stuff with core data on the background moc
self.backgroundContext = [[NSManagedObjectContext alloc] init];
[self.backgroundContext setParentContext:self.parentContext];
NSError *objectIDRetrievalError = nil;
self.objectInBackgroundContext = [self.backgroundContext existingObjectWithID:self.objectID error:&objectIDRetrievalError];
...
// take the map snapshot
...
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block MKMapSnapshot *result;
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
...
result = snapshot;
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
self.snapshot = result;
}
答案 2 :(得分:0)
如果您正在执行NSOperation
并发,正如您的代码所示,您基本上无法控制使用哪个线程来执行操作start
方法
这使后者变得复杂。
因此,我建议使用NSPrivateQueueConcurrencyType
并发类型创建 backgroundContext ,如下所示:
self.backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
(见NSManagedObjectContext: Concurrency)。
这使您可以在托管对象上下文中执行适当的执行上下文中的任何代码":
[self.backgroundContext performBlock: ^{
// executing on the background context's private queue
...
}];
[self.backgroundContext performBlockAndWait: ^{
// executing on the background context's private queue
...
}];
然后,在您的start
方法中,您可以调用"快照键盘"如下:
...
[snapshotter startWithQueue:dispatch_get_global_queue(0, 0)
completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
[self.backgroundContext performBlock:^{
// executing on the background context's private queue
...
self.snapshot = snapshot;
// here you likely need to orderly "terminate" the operation queue:
dispatch_async(_syncQueue, ^{
// set operation result:
if (error) { ... }
[self terminate];
}
}];
}];
terminate
的实施可能如下所示:
- (void) terminate {
self.isExecuting = NO;
self.isFinished = YES;
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = _result;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result);
});
}
}