2012-05-03 2 views
1

먼저 UIManagedDocument에 데이터를로드 한 다음 saveToURL:forSaveOperation:completionHandler:을 실행하는 응용 프로그램이 있습니다. completionHandler 블록 내부에서이 데이터베이스의 다양한 요소에 대한 업데이트를 수행하고 완료되면 다른 저장을 수행합니다.UIManagedDocument를 저장할 때 응용 프로그램이 손상됩니다.

게다가 앱에는 데이터를 다시로드하고 데이터를 다시 업데이트하며 데이터베이스의 한 엔티티를 삭제하는 3 개의 버튼이 있습니다. 모든 버튼 방법에서 마지막 명령도 절약 효과가 있습니다.

이 모든 것을 시뮬레이터에서 실행할 때 모두 원활하게 진행됩니다. 그러나 장치에서는 그렇지 않습니다. 끊임없이 충돌합니다. 나는 일반적으로 "삭제"버튼을 누르거나 데이터베이스를 다시로드하거나 다시 업데이트 할 때 충돌이 일어나는 것을 관찰했습니다. 그리고 항상 작동은 saveToURL입니다.
다중 스레드 데이터베이스를 저장하면 문제가 발생합니다. 기기가 코드를 더 느리게 실행하면 어쩌면 여러 절약 효과가 동시에 발생하고 앱이 올바르게 처리하지 못할 수 있습니다. 또한 삭제 버튼으로 엔티티가 삭제되지 않고 존재하지 않는다고 표시되는 경우도 있습니다.

나는 이것에 완전히 의아해하고 있으며,이 모든 저축 작업이 완료되어야합니다 ... 사실, 내가 제거하면, 앱은 더 모호하게 행동합니다.

이 문제를 해결하기 위해 무엇을 할 수 있습니까? 고마워요!

[편집] 여기서 문제가되는 코드를 게시합니다. 먼저 데이터를로드, 나는 특히이 두 가지 방법으로, 헬퍼 클래스를 사용

+ (void)loadDataIntoDatabase:(UIManagedDocument *)database 
{ 
    [database.managedObjectContext performBlock:^{ 
     // Read from de plist file and fill the database 
     [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { 
      [DataHelper completeDataOfDatabase:database]; 
     }]; 
} 

+ (void)completeDataOfDatabase:(UIManagedDocument *)database 
{ 
    [database.managedObjectContext performBlock:^{ 

     // Read from another plist file and update some parameters of the already existent data (uses NSFetchRequest and works well) 

     // [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil]; 
     [database updateChangeCount:UIDocumentChangeDone]; 

    }]; 
} 

그리고보기

, 나는이 같은 3 개 액션 방법이 있습니다

- (IBAction)deleteButton { 

    [self.database.managedObjectContext performBlock:^{ 
     NSManagedObject *results = ;// The item to delete 
     [self.database.managedObjectContext deleteObject:results]; 

      // [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL]; 
     [self.database updateChangeCount:UIDocumentChangeDone]; 
     }]; 
} 

- (IBAction)reloadExtraDataButton { 

    [DataHelper loadDataIntoDatabase:self.database]; 

    // [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL]; 
    [self.database updateChangeCount:UIDocumentChangeDone]; 

} 

- (IBAction)refreshDataButton { 

    [DataHelper completeDataOfDatabase:self.database]; 
    //[self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL]; 
    [self.database updateChangeCount:UIDocumentChangeDone]; 
} 

[편집 2 보다 코드 : 우선은 초기 뷰 viewDidLoad에이 방법을 실행 :

- (void)viewDidLoad{ 
    [super viewDidLoad]; 
    self.database = [DataHelper openDatabaseAndUseBlock:^{ 
     [self setupFetchedResultsController]; 
    }]; 
} 

이것은 setupFetchedResultsController 방법 모습이다 같은

- (void)setupFetchedResultsController 
{ 
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Some entity name"]; 
    request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]]; 

    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request 
                     managedObjectContext:self.database.managedObjectContext 
                      sectionNameKeyPath:nil 
                        cacheName:nil]; 
} 

데이터베이스의 다른 엔티티를 보여주기 위해 앱의 각보기 (탭 있음)에는 다른 setupFetchedResultsController가 있습니다.

지금, 헬퍼 클래스, 이것은 각보기의 viewDidLoad에를 통해 실행됩니다 일류 방법 :

+ (UIManagedDocument *)openDatabaseAndUseBlock:(completion_block_t)completionBlock 
{ 
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
    url = [url URLByAppendingPathComponent:@"Database"]; 
    UIManagedDocument *database = [[UIManagedDocument alloc] initWithFileURL:url]; 

    if (![[NSFileManager defaultManager] fileExistsAtPath:[database.fileURL path]]) { 

     [database saveToURL:database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { 
      [self loadDataIntoDatabase:database]; 
      completionBlock(); 
     }]; 

    } else if (database.documentState == UIDocumentStateClosed) { 
     // Existe, pero cerrado -> Abrir 
     [database openWithCompletionHandler:^(BOOL success) { 
      [self loadDataIntoDatabase:database]; 
      completionBlock(); 
     }]; 

    } else if (database.documentState == UIDocumentStateNormal) { 
     [self loadDataIntoDatabase:database]; 
     completionBlock(); 
    } 

    return database; 
} 

답변

4

당신은 정말 많은 코드를 제공하지 않았다. 당신이 준 유일한 단서는 당신이 다중 스레드를 사용하고 있다는 것입니다.

UIManagedDocument에는 2 개의 ManagedObjectContext (기본 대기열에 대해 지정된 대기열과 개인 대기열에 대해 지정된 하나의 대기열)가 있지만 각 대기열 내에서만 여전히 액세스해야합니다.

따라서 메인 스레드 내에서 managedDocument.managedObjectContext 만 사용해야합니다. 다른 스레드에서 사용하려면 performBlock 또는 performBlockAndWait를 사용해야합니다. 마찬가지로 부모 컨텍스트에 대해 전용 스레드에서 실행 중인지 결코 알 수 없으므로 부모에게 특정 작업을 수행하려면 performBlock *을 사용해야합니다.

마지막으로 데이터베이스를 처음 만들 때를 제외하고는 saveToURL을 호출하면 안됩니다. UIManagedDocument는 (자동으로) 자동 저장됩니다.

이전에 저장하도록 권장하려면 updateChangeCount : UIDocumentChangeDone을 ​​보내서 저장해야하는 변경 사항이 있음을 알리십시오.

편집

당신이 아주 처음으로 파일을 만들 때 만 saveToURL를 호출해야합니다. UIManagedDocument를 사용하면 다시 호출 할 필요가 없습니다 (실제로 의도하지 않은 일부 문제가 발생할 수 있음).

기본적으로 문서를 만들 때 완성 처리기가 실행될 때까지 iVar를 설정하지 마십시오. 그렇지 않으면 부분 상태의 문서를 사용할 수 있습니다. 이 경우 완료 핸들러에서이와 같은 도우미를 사용하십시오.

- (void)_document:(UIManagedDocument*)doc canBeUsed:(BOOL)canBeUsed 
{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     if (canBeUsed) { 
      _document = doc; 
      // Now, the document is ready. 
      // Fire off a notification, or notify a delegate, and do whatever you 
      // want... you really should not use the document until it's ready, but 
      // as long as you leave it nil until it is ready any access will 
      // just correctly do nothing. 
     } else { 
      _document = nil; 
      // Do whatever you want if the document can not be used. 
      // Unfortunately, there is no way to get the actual error unless 
      // you subclass UIManagedDocument and override handleError 
     } 
    }]; 
} 

그리고 문서, 같은 뭔가를 ... 초기화 할 수

- (id)initializeDocumentWithFileURL:(NSURL *)url 
{ 
    if (!url) { 
     url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
     url = [url URLByAppendingPathComponent:@"Default_Project_Database"]; 
    } 
    UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:url]; 

    if (![[NSFileManager defaultManager] fileExistsAtPath:[doc.fileURL path]]) { 
     // The file does not exist, so we need to create it at the proper URL 
     [doc saveToURL:doc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { 
      [self _document:doc canBeUsed:success]; 
     }]; 
    } else if (doc.documentState == UIDocumentStateClosed) { 
     [doc openWithCompletionHandler:^(BOOL success) { 
      [self _document:doc canBeUsed:success]; 
     }]; 
    } else { 
     // You only need this if you allow a UIManagedDocument to be passed 
     // in to this object -- in which case the code above that initializes 
     // the <doc> variable will be conditional on what was passed... 
     BOOL success = doc.documentState == UIDocumentStateNormal; 
     [self _document:doc canBeUsed:success]; 
    } 
} 

은 "패턴"위가 사용하기에 완벽하게 준비가 될 때까지 문서를 사용하지 않도록 할 필요가있다. 이제 코드 조각 만 saveToURL을 호출해야합니다.

정의에 따르면 document.managedObjectContext는 NSMainQueueConcurrencyType 유형입니다. 따라서 모든 UI 콜백과 같이 코드가 주 스레드에서 실행되고있는 경우 performBlock을 사용할 필요가 없습니다. 실제로 백그라운드에서로드를하고 있다면

그러나 ..

- (void)backgroundLoadDataIntoDocument:(UIManagedDocument*)document 
{ 
    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    moc.parentContext = document.managedObjectContext; 
    [moc performBlock:^{ 
     // Do your loading in here, and shove everything into the local MOC. 
     // If you are loading a lot of stuff from the 'net (or elsewhere), 
     // consider doing it in strides, so you deliver objects to the document 
     // a little at a time instead of all at the end. 

     // When ready to save, call save on this MOC. It will shove the data up 
     // into the MOC of the document. 
     NSrror *error = nil; 
     if ([moc save:&error]) { 
      // Probably don't have to synchronize calling updateChangeCount, but I do it anyway... 
      [document.managedObjectContext performBlockAndWait:^{ 
       [document updateChangeCount:UIDocumentChangeDone]; 
      }]; 
     } else { 
      // Handle error 
     } 
    }]; 
} 

대신, 당신은 parentContext에 부모가 수있는 mainMOC에 배경 MOC 양육의 생각. 로딩 한 다음 저장하면 변경 사항이 주 MOC의 "위"에 배치됩니다. 메인 MOC는 다음에 패치 작업을 수행 할 때 그 변경 사항을 볼 것이다 (NSFetchRequest의 속성을 주목하라).

참고 : 어떤 사람들은 Erica Sadun의 저서에있는 메모로 보입니다. 첫 번째 saveToURL 이후에 닫은 다음 열어야 모든 것이 제대로 작동합니다.

편집

이 정말 긴지고 있습니다. 포인트가 더 많으면 채팅을 제안합니다. 사실, 우리는 그렇게 할 수 없지만 다른 매체를 통해 할 수 있습니다. 나는 짧게하려고 노력할 것이다. 그러나 되돌아 가게되고, 내가 게시 한 것을 다시 읽고, 당신의 코드가 여전히 여러 임차인을 침해하고 있기 때문에 조심해야한다.

먼저 viewDidLoad()에서 openDatabaseAndUseBlock을 호출 한 결과에 직접 문서를 할당합니다. 문서 은 사용 가능한 상태가 아닙니다. OpenDatabaseAndUseBlock()이 반환되기 전에는 완료 핸들러가 시작될 때까지 문서에 액세스 할 수 없도록하려고합니다.

둘째, openDatabaseAndUseBlock() 내에서 데이터베이스를 처음 만들 때 saveToURL 만 호출하십시오. 다른 곳에서는 사용하지 마십시오.

셋째. 알림 센터에 등록하여 모든 이벤트를 수신하십시오 (그냥 기록하십시오).무슨 일이 일어나고 있는지 알 수 있으므로 디버깅을 크게 도와줍니다.

넷째, UIManagedDocument를 서브 클래스로 만들고 handleError를 오버라이드하여 호출되는지 확인하십시오. 발생하는 경우 정확한 NSError를 볼 수있는 유일한 방법입니다.

3/4는 주로 프로덕션 코드에 필요하지 않은 디버그를 돕는 데 사용됩니다.

약속이 있으므로 지금 중단해야합니다. 그러나 문제를 해결하려면 여기를 클릭하십시오

+0

답장을 보내 주셔서 감사합니다. 방금 코드를 포함하도록 게시물을 편집하여 더 잘 이해했습니다. 나는 'updateChangeCount'를 가진 첫 번째 것을 제외하고는 모든 절감액을 변경했으나 버튼 (거의 항상 삭제 버튼)을 눌렀을 때 애플 리케이션을 고정시키지 않는 것으로 보입니다. 또한, 데이터베이스가 아직 저장되지 않았기 때문에 삭제할 항목이 없다고 임의로 말합니다. 항목을 찾으라는 요청이 정확하다고 확신합니다. – David

+0

당신은 여전히 ​​그걸 제대로 가지고 있지 않습니다. 불행히도 저녁 7시에 친구가 있어야하는데 6시 43 분이고 집에서 45 분입니다. 나는 또한 코드 스 니펫이 암시하는 것보다 더 많은 일이 일어나고 있다는 느낌을 가지고 있습니다. 현재하고있는 일에 대해 좀 더 게시 할 수 있다면, 오늘 밤 늦게까지하고있는 것과 비슷한 UIManagedDocument 예제를 함께 넣을 수 있는지 알게 될 것입니다. –

+0

다시 한번 고마워, 조디! 더 많은 코드를 표시하기 위해 게시물을 다시 편집했습니다. 희망은 당신이 더 나은 아이디어를 만드는 데 도움이! 내 코드는 게시물의 내용과 매우 흡사합니다. 나는'loadDataIntoDatabase :'의 내용을'dispatch_async (dispatch_get_main_queue(),^{}) '에 넣으려고했지만 아무 것도 변하지 않는 것 같습니다. 어쩌면 나는이 모든 것을 나쁜 방식으로 접근하고있을 것이다 ... 나는 단지 데이터베이스가 한번 데이터로 채워지기를 원한다. 그런 다음 버튼을 누를 때 매개 변수를 업데이트하고, 너무 자주 수정되는 다른 파일에서 매개 변수를 읽는다. 너무 복잡해서는 안된다, 나는 생각한다 ... : S – David

관련 문제