6

単一の共有 UIManagedDocumentを使用して Core Data で Justin Driscoll の実装を使用しています。iPhone アプリでは、iPad ストーリーボードと iPad アプリ用の分割ビュー コントローラーに移動するまで、すべて問題ありませんでした。問題は、openwithCompletionHandler が 2 回呼び出されていることです。1 回目は viewDidLoad のマスター ビューから、もう 1 回は詳細ビュー viewWillLoad です。呼び出しは立て続けに行われ、ドキュメントはまだ UIDocumentStateClosed にあるため、シングルトンの performWithDocument メソッド (以下) に対して 2 回目の呼び出しが行われると、アプリがクラッシュします。iOS5.1 の投稿に対する e_x_p の回答を見ました: タスクの同期 (完了を待ちます)ただし、以下の performWithDocument が同じスレッドで呼び出されるため、この場合 @sychronized は機能しません。openwithCompletionHandler への複数の呼び出しから保護するにはどうすればよいですか? これを防ぐために考えられる唯一の方法は、UIDocumentStateNormal が true であると確信できるまで上記のいずれかの呼び出しの実行を一時停止してから解放することです。ただし、メインの 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

ジャスティンが上記で提案したように、私はそれを行いました。私のアプリの 1 つで、約 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];
    }
}

Swift (あまりテストされていません)

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

これは興味深いことであり、間違いなく私のコードの欠陥です (申し訳ありません!)。私が最初に考えたのは、シリアル キューをプロパティとしてドキュメント ハンドラー クラスに追加し、そのチェックを実行することです。

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 を再度呼び出すことができます。

于 2013-04-16T20:17:57.247 に答える
0

numberOfRowsInSection:と の間で共有されるコードのブロックは、cellForRowAtIndexPath:1 回だけ呼び出す必要があります。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 に答える