Ios UIManagedDocument单例代码openWithCompletionHandler调用两次并崩溃
我正在使用Justin Driscoll的实现。 我的iphone应用程序一切都很好,直到我将其移动到iPad情节提要和iPad应用程序的splitview控制器。问题是openwithCompletionHandler被调用了两次,一次从viewDidLoad中的主视图调用,另一次从viewWillLoad中的详细视图调用。调用是快速连续的,因为当第二次调用singleton的performWithDocument方法(如下)时,文档仍处于UIDocumentStateClosed状态,所以应用程序崩溃。我查看了e_x_p对post的回答,但是@synchronized在这种情况下不起作用,因为performWithDocument下面是在同一个线程上调用的。如何防止多次调用openwithCompletionHandler?我认为防止这种情况发生的唯一方法是暂停执行上述调用之一,直到我确定UIDocumentStateNormal为true,然后释放。但是这会冻结主UI线程,这是不好的。在不冻结UI的情况下,最好的方法是什么 从UIManagedDocumentSingleton代码:Ios UIManagedDocument单例代码openWithCompletionHandler调用两次并崩溃,ios,singleton,uimanageddocument,Ios,Singleton,Uimanageddocument,我正在使用Justin Driscoll的实现。 我的iphone应用程序一切都很好,直到我将其移动到iPad情节提要和iPad应用程序的splitview控制器。问题是openwithCompletionHandler被调用了两次,一次从viewDidLoad中的主视图调用,另一次从viewWillLoad中的详细视图调用。调用是快速连续的,因为当第二次调用singleton的performWithDocument方法(如下)时,文档仍处于UIDocumentStateClosed状态,所以应
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success)
{
onDocumentReady(self.document);
};
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]])
{
//This should never happen*******************
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
}
在
numberOfRowsInSection:
和cellforrowatinexpath:
之间共享的代码块只能调用一次numberOfRowsInSection
总是在tableView
尝试渲染单元格之前被调用,因此您应该创建一个NSArray
对象,您可以将提取请求的结果存储到其中,然后在渲染单元格时使用此数组:
@implementation FooTableViewController {
NSArray *_privateArray;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
[[UIManagedDocumentSingletonHandler sharedDocumentHandler] performWithDocument:^(FCUIManagedDocumentObject *document) {
NSManagedObjectContext * context = document.managedObjectContext;
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"FCObject"];
NSPredicate * searchStringPredicate = nil;
if (searchFilterString)
{
searchStringPredicate = [NSPredicate predicateWithFormat:@"word BEGINSWITH[c] %@",searchFilterString];
}
request.predicate = searchStringPredicate;
request.shouldRefreshRefetchedObjects = YES;
NSError * error;
_privateArray = [context executeFetchRequest:request error:&error];
}];
return _privateArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"FCCell";
FCardCell *cell = (FCCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
FCManagedObject * fcc = [_privateArray objectAtIndex:indexPath.row];
cell.isWordVisible.on = fcc.isUsed;
cell.fWord.text = fcc.word;
return cell;
}
我不确定您是否需要对NSArray
执行一些特殊操作以在块内设置它(la\uu块
)
这样做的主要原因是,您需要确保用于确定行数的数据集在100%的时间内与创建单元格时的大小相同。如果他们不匹配,你就会崩溃。此外,由于您没有块,因此不需要立即分派来进行UITableViewCell
更新
最后,如果
UIDocumentStateClosed
导致问题,您应该从NSFetch
结果中筛选出这些问题(附加谓词,如果需要,请参阅NSCompoundPredicate
),或者让代码在cellForRowAtIndexPath:
中更好地处理它们。这很有趣,而且肯定是我代码中的一个缺陷(抱歉!)。我的第一个想法是将串行队列作为属性添加到文档处理程序类中,并对其执行检查
self.queue = dispatch_queue_create("com.myapp.DocumentQueue", NULL);
然后在performWithDocument中:
dispatch_async(self.queue, ^{
if (![[NSFileManager defaultManager] fileExistsAtPath... // and so on
});
但那也不行
您可以在调用saveToURL时设置一个BOOL标志并在回调中清除它。然后您可以检查该标志,并在稍后使用performSelectorAfterDelay调用performWithDocument(如果正在创建文件)。我按照Justin的建议做了这件事。在我的一个应用程序中,使用约20k个用户两年,效果很好
@interface SharedUIManagedDocument ()
@property (nonatomic)BOOL preparingDocument;
@end
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
onDocumentReady(self.document);
self.preparingDocument = NO; // release in completion handler
};
if(!self.preparingDocument) {
self.preparingDocument = YES; // "lock", so no one else enter here
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
} else {
// try until document is ready (opened or created by some other call)
[self performSelector:@selector(performWithDocument:) withObject:onDocumentReady afterDelay:0.5];
}
}
Swift(测试不多)
我已将提取请求合并为cellForRowAtIndexPath和numberOfRowsInSection现在使用的单个初始调用,因此不再存在问题,但问题仍然存在。我已更新问题以简化它。您能否
@synchronized(documentObject)
在对象本身上同步?除非这样,否则您可以从master/detail委托权限,这样只有其中一个需要进行调用(或者如果他们有保证的执行顺序,您可以在他们之间传递消息(可能通过@protocol
),以便在第一次调用完成后在第二次执行。“//”锁定“,所以没有其他人进入这里”-这是一个非常大的问题mistake@adnako如果你说的是语言学,我同意“锁”“这不是一个好词,只要它只是一个标志。但如果你认为这种方法有缺点,请解释,我真的很感兴趣!不,我说的是多线程。这段代码应该只在单队列中工作。我也使用这种模式,现在我正试图解决调用的问题。”[self.document openWithCompletionHandler:OnDocumentDidLoad];两次。@adnako它是一个单例,用GCD的dispatch_实现一次(例如在有效的Objective-C 2.0中描述)。因此我们有iOS的保证(你可能会争辩,但这不是问题)在这里,该实例是唯一一个带有dispatch_once代码的实例。这给我们留下了唯一一个self.preparingDocument变量。您不明白。dispatch_once只在静态ivar创建上起作用。任何其他多线程问题都很好。稍后我将向您发送一个简单的示例。
typealias OnDocumentReady = (UIManagedDocument) ->()
class SharedManagedDocument {
private let document: UIManagedDocument
private var preparingDocument: Bool
static let sharedDocument = SharedManagedDocument()
init() {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let documentsDirectory: NSURL = urls.first as! NSURL
let databaseURL = documentsDirectory.URLByAppendingPathComponent(".database")
document = UIManagedDocument(fileURL: databaseURL)
let options = [NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true]
document.persistentStoreOptions = options
preparingDocument = false
}
func performWithDocument(onDocumentReady: OnDocumentReady) {
let onDocumentDidLoad:(Bool) ->() = {
success in
onDocumentReady(self.document)
self.preparingDocument = false
}
if !preparingDocument {
preparingDocument = true
if !NSFileManager.defaultManager().fileExistsAtPath(document.fileURL.path!) {
println("Saving document for first time")
document.saveToURL(document.fileURL, forSaveOperation: .ForCreating, completionHandler: onDocumentDidLoad)
} else if document.documentState == .Closed {
println("Document closed, opening...")
document.openWithCompletionHandler(onDocumentDidLoad)
} else if document.documentState == .Normal {
println("Opening document...")
onDocumentDidLoad(true)
} else if document.documentState == .SavingError {
println("Document saving error")
} else if document.documentState == .EditingDisabled {
println("Document editing disabled")
}
} else {
// wait until document is ready (opened or created by some other call)
println("Delaying...")
delay(0.5, closure: {
self.performWithDocument(onDocumentReady)
})
}
}
private func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
}