UIManagedDocument单例代码openWithCompletionHandler调用两次并崩溃
我正在使用Justin Driscoll的核心数据实现与单个共享的UIManagedDocument 。 一切都很好,在我的iPhone应用程序,直到我把它移动到iPad的故事板和iPad的应用程序的分割控制器。 问题是openwithCompletionHandler被调用两次,一次从viewDidLoad中的主视图,再次在我的详细视图viewWillLoad。 调用是快速连续的,因为当第二次调用我的performWithDocument方法(下面)的单例的应用程序崩溃时,文档仍然在UIDocumentStateClosed。 我查看了iOS5.1后的e_x_p的答案:同步任务(等待完成),但是在这种情况下@sychronized不起作用,因为下面的performWithDocument是在同一个线程上调用的。 我将如何防止多次调用openwithCompletionHandler? 我能想到的防止这种情况的唯一方法是暂停执行上面的一个调用,直到我确定UIDocumentStateNormal为true,然后释放。 这虽然会冻结主要的UI线程是不好的。 如果不冻结用户界面,最好的办法是什么?
从UIManagedDocumentSingleton代码:
- (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); } }
我是按照Justin 上面的build议做的。 在我的一个应用程序工作正常,两年约2万用户。
@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 (没有太多testing)
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) } }
这是有趣的,我的代码绝对是一个缺陷(对不起!)。 我的第一个想法是将一个串行队列作为属性添加到您的文档处理程序类,并执行检查。
self.queue = dispatch_queue_create("com.myapp.DocumentQueue", NULL);
然后在执行WithDocument:
dispatch_async(self.queue, ^{ if (![[NSFileManager defaultManager] fileExistsAtPath... // and so on });
但是这也行不通
你可以设置一个BOOL标志,当你调用saveToURL并在callback中清除它。 然后你可以检查那个标志并且使用performSelectorAfterDelay在文件被创build的时候再次调用performWithDocument。
在numberOfRowsInSection:
和cellForRowAtIndexPath:
之间共享的代码块只能被调用一次。 在tableView
试图呈现单元格之前,总是会调用numberOfRowsInSection
,所以你应该创build一个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
做一些特殊的事情来设置它( __block
),我不能确定。
造成这种情况的主要原因是您需要确保数据集用于确定行数的100%与创build单元格的大小相同。 如果他们不匹配,那么你会崩溃。 此外,由于您没有块,所以您不需要立即调度来创buildUITableViewCell
更新。
最后,如果UIDocumentStateClosed
导致问题,您应该将它们从NSFetch
结果中筛选出来(如果需要,请参阅NSCompoundPredicate
),或者让代码在cellForRowAtIndexPath:
更好地处理它们cellForRowAtIndexPath: