2012-11-20 26 views
6

Używam implementacji Justina Driscolla na Core Data with a Single Shared UIManagedDocument. Wszystko było dobrze w mojej aplikacji na iPhone'a, dopóki nie przeniosłem go do storyboardu na iPada i kontrolera splitview dla aplikacji ipad. Problem polega na tym, że openwithCompletionHandler jest wywoływany dwa razy, jeden raz z mojego widoku głównego w viewDidLoad i ponownie w widoku szczegółów viewWillLoad. Połączenia są wykonywane w krótkich odstępach czasu, a ponieważ dokument jest nadal w UIDocumentStateClosed, gdy drugie wywołanie zostanie wykonane do mojej metody performWithDocument (poniżej) singleton aplikacja ulega awarii. Spojrzałem na odpowiedź e_x_p dla posta iOS5.1: synchronising tasks (wait for a completion), ale @sychronized nie zadziała w tym przypadku, ponieważ performWithDokument poniżej jest wywoływany w tym samym wątku. W jaki sposób miałbym chronić przed wieloma wywołaniami do openwithCompletionHandler? Jedynym sposobem, jaki mogę ochronić przed tym, jest wstrzymanie wykonywania jednego z powyższych połączeń, dopóki nie upewnię się, że UIDocumentStateNormal jest prawdziwe, a następnie zostanie zwolnione. To jednak zamroziłoby główny wątek UI, który nie jest dobry. Co jednak byłoby najlepszym sposobem na to, bez zamrożenia interfejsu użytkownika?UIManagedDocument Singleton Code openWithCompletionHandler zadzwonił dwukrotnie i zawiesił się

z kodu 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); 
    } 
} 

Odpowiedz

0

blok kodu, który jest podzielony między numberOfRowsInSection: i cellForRowAtIndexPath: powinna być wywoływana tylko raz. numberOfRowsInSection zawsze będzie wezwany przed tableView stara się uczynić komórki, więc należy utworzyć obiekt NSArray że można przechowywać wyniki życzenie zwrcania do, a następnie użyć tej tablicy przy renderowaniu swoje komórki:

@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; 
} 

Nie jestem pewna, czy mam ochotę zrobić coś specjalnego z NSArray, aby ustawić go wewnątrz bloku (a la __block).

Głównym powodem tego jest konieczność upewnienia się, że 100% czasu zestaw danych używany do określenia liczby wierszy ma taki sam rozmiar jak podczas tworzenia komórek. Jeśli nie pasują, nastąpi awaria. Także dlatego, że nie masz bloku, którego nie potrzebujesz teraz, aby dokonać aktualizacji UITableViewCell.

Wreszcie jeśli UIDocumentStateClosed powoduje problemy należy albo je odfiltrować z twoich NSFetch wyników (dodatkowe orzecznika, zobacz NSCompoundPredicate jeśli jest wymagana) lub mieć kod, aby obsługiwać je lepiej w cellForRowAtIndexPath:

+0

Mam skonsolidowane wniosek pobierał do jednego pierwszego połączenia, które cellForRowAtIndexPath i numberOfRowsInSection teraz wykorzystać w ten sposób nie mają tam problem już jednak problem nadal istnieje. Zaktualizowałem ten problem, aby go uprościć. –

+0

Czy mógłbyś 'zsynchronizować (documentObject)', aby zsynchronizować się z samym obiektem? Blokowanie w taki sposób, że możesz delegować uprawnienia od głównego/szczegółu, tak aby tylko jeden z nich musiał wykonać połączenie (lub jeśli mają zagwarantowaną kolejność wykonywania, możesz przekazać wiadomość między nimi (najprawdopodobniej przez '@ protokół'). Wykonaj na drugim, gdy pierwszy zostanie zrobiony –

1

to ciekawe i zdecydowanie wada w moim kod (przepraszam!). Moją pierwszą myślą byłoby dodanie kolejki szeregowej jako własności do klasy obsługi dokumentu i wykonanie sprawdzenia tego.

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

a następnie w performWithDocument:

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

Ale to nie działa albo ...

Można ustawić flagę BOOL kiedy zadzwonić saveToURL i wyczyścić go w zwrotnego. Następnie możesz sprawdzić tę flagę i użyć performSelectorAfterDelay, aby ponownie wywołać performWithDocument nieco później, jeśli plik jest tworzony.

2

Zrobiłem to jako Justin zasugerował powyżej poniżej. Działa dobrze w jednej z moich aplikacji przez dwa lata z ~ 20 tys. Użytkowników.

@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 (niedużo testowane)

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) 
} 
} 
+1

Dobrze, dziękuję za napisanie tego Vladimir! – Corey

+0

"//" blokada ", więc nikt tu nie wchodzi" - To bardzo duży błąd – adnako

+0

@adnako jeśli mówisz o językoznawstwie, Zgadzam się, że "blokada" nie jest tu dobrym słowem, o ile jest tylko flagą, ale jeśli uważasz, że to podejście ma słabe strony, wyjaśnij, jestem naprawdę zainteresowany! –