6

我正在使用 Justin Driscoll 在Core Data 上的实现和单个共享 UIManagedDocument。在我将它移到 iPad 故事板和 ipad 应用程序的 splitview 控制器之前,我的 iphone 应用程序中的一切都很好。问题是 openwithCompletionHandler 被调用了两次,一次是在 viewDidLoad 的主视图中,一次是在我的详细视图 viewWillLoad 中。这些调用是快速连续的,因为当第二次调用我的单例的 performWithDocument 方法(如下)时,文档仍处于 UIDocumentStateClosed 中,因此应用程序崩溃了。我查看了 e_x_p 对 iOS5.1 后的回答:同步任务(等待完成)但是@sychronized 在这种情况下将不起作用,因为下面的 performWithDocument 是在同一个线程上调用的。我将如何防止对 openwithCompletionHandler 的多次调用?我能想到的防止这种情况的唯一方法是暂停执行上述调用之一,直到我确定 UIDocumentStateNormal 为真,然后释放。但这会冻结不好的主 UI 线程。在不冻结 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);
    }
}
4

3 回答 3

2

我按照上面贾斯汀的建议做了。在我的一个应用程序中运行良好两年,有大约 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];
    }
}

斯威夫特(没有太多测试)

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)
}
}
于 2013-06-18T15:02:12.460 回答
1

That's interesting and definitely a flaw in my code (sorry!). My first thought would be to add a serial queue as a property to your document handler class and perform the check on that.

self.queue = dispatch_queue_create("com.myapp.DocumentQueue", NULL);

and then in performWithDocument:

dispatch_async(self.queue, ^{
    if (![[NSFileManager defaultManager] fileExistsAtPath... // and so on
});

But that wouldn't work either...

You could set a BOOL flag when you call saveToURL and clear it in the callback. Then you can check for that flag and use performSelectorAfterDelay to call performWithDocument again a little later if the file is being created.

于 2013-04-16T20:17:57.247 回答
0

之间共享的代码块,numberOfRowsInSection:应该cellForRowAtIndexPath:只调用一次。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如果你需要做一些特别的事情来将它设置在块内(a la __block),我不确定我的头顶。

这样做的主要原因是您需要确保用于确定行数的数据集 100% 的时间与您创建单元格时的大小相同。如果它们不匹配,那么您将崩溃。此外,由于您没有块,因此您现在不需要调度进行UITableViewCell更新。

最后,如果UIDocumentStateClosed导致问题,您应该将它们从NSFetch结果中过滤掉(附加谓词,看看NSCompoundPredicate是否需要),或者有代码来更好地处理它们cellForRowAtIndexPath:

于 2012-11-21T05:21:03.733 回答