2012-03-19 2 views
2

Ok. 이 하나가 나를 벽으로 몰아 넣고있다. 나는UIManagedDocument의 상위 NSManagedObjectContext에 대한 백그라운드 큐 변경으로 인해 NSFetchedresultsController가 중복됩니다.

  • UIManagedDocument과 2 MOContexts이 (일반 및 부모를.)
  • 있는 UITableViewController이는
  • NSFetchedResultsController
  • 오프 실행 (폴 Hegarty에 의해 CoreDataTableViewController에 서브 클래스) 동기화에 대 한 배경 GCD 큐 부모 큐가 액세스하는 서버와 함께

나는 이것을 여러 가지 방법으로 시도해 봤는데 매번 문제가 생겼다.

"동물"엔티티를 새로 추가 할 때 아무런 문제가없고 즉시 테이블에 나타납니다. 하지만 업로드 대기열에있는 서버로 업로드하고 업로드 된 섹션에 있어야하도록 부모 컨텍스트가있는 "상태"를 변경하면 거기에 나타나지만 업로드되지 않은 섹션에서는 사라지지 않습니다. .

내가 끝내지 않은 2 개로 끝났다. 또는 심지어 때로는 올바른 것을 만들지 않고 단지 잘못된 것을 유지합니다.

***하지만 앱을 종료하고 다시로드하면 추가 정보가 사라집니다. 그래서 그것은 어딘가에있는 기억에 있습니다. 나는 상점에서 모든 것이 정확하다는 것을 확인할 수 있습니다. 하지만 NSFetchedResultsController는 controllerDidChange ... stuff를 실행하지 않습니다. 누군가를 통해 웨이드 할 수 있도록

- (NSArray *)sectionHeaderTitles 
{ 
if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:@"Not Yet Uploaded", @"Uploaded But Not Featured", @"Previously Featured", nil]; 
return _sectionHeaderTitles; 
} 

- (NSDictionary *)selectedEntry 
{ 
if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init]; 
return _selectedEntry; 
} 

- (void)setupFetchedResultsController 
{ 
[self.photoDatabase.managedObjectContext setStalenessInterval:0.0]; 
[self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0]; 
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Animal"]; 
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"unique" ascending:NO], nil]; 

self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:@"status" cacheName:nil]; 
NSError *error; 
BOOL success = [self.fetchedResultsController performFetch:&error]; 
if (!success) NSLog(@"error: %@", error); 
else [self.tableView reloadData]; 
self.fetchedResultsController.delegate = self; 
} 

- (void)useDocument 
{ 
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) { 
    [self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { 
     [self setupFetchedResultsController]; 
    }]; 
} else if (self.photoDatabase.documentState == UIDocumentStateClosed) { 
    [self.photoDatabase openWithCompletionHandler:^(BOOL success) { 
     [self setupFetchedResultsController]; 

    }]; 

} else if (self.photoDatabase.documentState == UIDocumentStateNormal) { 
    [self setupFetchedResultsController]; 
} 
} 

- (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase 
{ 
if (_photoDatabase != photoDatabase) { 
    _photoDatabase = photoDatabase; 
    [self useDocument]; 
} 
} 

- (void)viewDidLoad 
{ 
[super viewDidLoad]; 

UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero]; 
label.backgroundColor = [UIColor clearColor]; 
label.font = [UIFont fontWithName:@"AmericanTypewriter" size:20]; 
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5]; 
label.textAlignment = UITextAlignmentCenter; 
label.textColor = [UIColor whiteColor]; 
self.navigationItem.titleView = label; 
label.text = self.navigationItem.title; 
[label sizeToFit]; 
} 

- (void)viewWillAppear:(BOOL)animated 
{ 
[super viewWillAppear:animated];  

// Get CoreData database made if necessary 
if (!self.photoDatabase) { 
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 
    url = [url URLByAppendingPathComponent:@"Default Photo Database"]; 
    self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url]; 
    NSLog(@"No existing photoDatabase so a new one was created from default photo database file."); 
} 

self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"DarkWoodBackGround.png"]]; 
} 


- (void)syncWithServer 
{ 
// This is done on the syncQ 

// Start the activity indicator on the nav bar 
dispatch_async(dispatch_get_main_queue(), ^{ 
    [self.spinner startAnimating]; 
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner]; 

    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(managedObjectContextDidSave:) 
               name:NSManagedObjectContextDidSaveNotification 
               object:self.photoDatabase.managedObjectContext.parentContext]; 
}); 


// Find new animals (status == 0) 
NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Animal"]; 
newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:@"status == 0"]; 
NSError *error; 
NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error]; 
if ([newAnimalsArray count]) NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]); 
if (error) NSLog(@"fetchError: %@", error); 

// Get the existing animals from the server 
NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb]; 

// In the parent context, insert downloaded animals into core data 

for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) { 
    [Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext]; 
    // table will automatically update due to NSFetchedResultsController's observing of the NSMOC 
} 

// Upload the new animals if there are any 
if ([newAnimalsArray count] > 0) { 
    NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]); 
    for (Animal *animal in newAnimalsArray) { 

     // uploadAnimal returns a number that lets us know if it was accepted by the server 
     NSNumber *unique = [self uploadAnimal:animal]; 
     if ([unique intValue] != 0) { 
      animal.unique = unique; 

      // uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully 
      if ([self uploadThePhotosOf:animal]){ 
       [self.photoDatabase.managedObjectContext performBlock:^{ 
        animal.status = [NSNumber numberWithInt:1]; 
       }]; 
      } 
     } 
    } 
} 

[self.photoDatabase.managedObjectContext.parentContext save:&error]; 
if (error) NSLog(@"Saving parent context error: %@", error); 
[self performUpdate]; 


// Turn the activity indicator off and replace the sync button 
dispatch_async(dispatch_get_main_queue(), ^{ 
    // Save the context 
    [self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { 
     if (success) 
     { 
      NSLog(@"Document was saved"); 
      [self.photoDatabase.managedObjectContext processPendingChanges]; 
      } else { 
      NSLog(@"Document was not saved"); 
     } 
    }]; 

    [self.spinner stopAnimating]; 
    self.navigationItem.leftBarButtonItem = self.syncButton; 
}); 

// Here it skips to the notification I got from saving the context so I can MERGE them 
} 

- (NSNumber *)uploadAnimal:(Animal *)animal 
{ 
NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL]; 
NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation]; 
NSLog(@"JSONRepresentation of %@: %@", animal.namestring, jsonStringFromAnimalMetaDictionary); 
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL]; 
[request setPostValue:jsonStringFromAnimalMetaDictionary forKey:@"newmeta"]; 
[request startSynchronous]; 
NSError *error = [request error]; 
NSString *response; 
if (!error) { 
    response = [request responseString]; 
    NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0]; 
    return animalUnique; 
} else { 
    response = [error description]; 
    NSLog(@"%@ got an error: %@", animal.namestring, response); 
    return [NSNumber numberWithInt:0]; 
} 
} 

- (BOOL)uploadThePhotosOf:(Animal *)animal 
{ 
NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL]; 

int index = [animal.photos count]; 
for (Photo *photo in animal.photos) { 

    // Name the jpeg file 
    NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate]; 
    NSString *imageServerPath = [NSString stringWithFormat:@"%lf-Photo.jpeg",timeInterval]; 

    // Update the imageServerPath 
    photo.imageURL = imageServerPath; 

    NSData *photoData = [[NSData alloc] initWithData:photo.image]; 
    NSString *photoMeta = [photo.metaDictionary JSONRepresentation]; 

    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL]; 
    [request addPostValue:photoMeta forKey:@"newphoto"]; 
    [request addData:photoData withFileName:imageServerPath andContentType:@"image/jpeg" forKey:@"filename"]; 
    [request setUploadProgressDelegate:self.progressView]; 
    [request startSynchronous]; 
    NSLog(@"%@ progress: %@", animal.namestring, self.progressView.progress); 

    NSString *responseString = [request responseString]; 
    NSLog(@"uploadThePhotosOf:%@ photo at placement: %d has responseString: %@", animal.namestring, [photo.placement intValue], responseString); 
    SBJsonParser *parser= [[SBJsonParser alloc] init]; 
    NSError *error = nil; 
    id jsonObject = [parser objectWithString:responseString error:&error]; 
    NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0]; 

    // A proper response is not 0 
    if ([parsedPhotoUploadResponse intValue] != 0) { 
     photo.imageid = parsedPhotoUploadResponse; 
     --index; 
    } 
} 

// If the index spun down to 0 then it was successful 
int success = (index == 0) ? 1 : 0; 
return success; 
} 

- (NSArray *)downloadedAllAnimalsFromWeb 
{ 
NSURL *downloadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL]; 
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL]; 
[request setPostValue:@"yes" forKey:@"all"]; 
request.tag = kGetHistoryRequest; 
[request startSynchronous]; 
NSString *responseString = [request responseString]; 
NSLog(@"downloadedAllAnimalsFromWeb responseString: %@", responseString); 

SBJsonParser *parser= [[SBJsonParser alloc] init]; 
NSError *error = nil; 
id jsonObject = [parser objectWithString:responseString error:&error]; 
NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject]; 
return parsedDownloadedResponseStringArray; 
} 

- (void)performUpdate 
{ 
NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext; 
NSSet     * inserts = [context updatedObjects]; 

if ([inserts count]) 
{ 
    NSError * error = nil; 

    NSLog(@"There were inserts"); 
    if ([context obtainPermanentIDsForObjects:[inserts allObjects] 
             error:&error] == NO) 
    { 
     NSLog(@"BAM! %@", error); 
    } 
} 

[self.photoDatabase updateChangeCount:UIDocumentChangeDone]; 
} 

- (void)managedObjectContextDidSave:(NSNotification *)notification 
{ 

[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext]; 

NSLog(@"userInfo from the notification: %@", [notification userInfo]); 
    // Main thread context 
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext; 

SEL selector = @selector(mergeChangesFromContextDidSaveNotification:); 
[context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES]; 
NSLog(@"ContextDidSaveNotification was sent. MERGED"); 

} 


#pragma mark - Table view data source 


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EntryCell"]; 

if (!cell) { 
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"EntryCell"]; 
} 

// Configure the cell here... 
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath]; 
cell.textLabel.text = animal.namestring; 
if (([animal.numberofanimals intValue] > 0) && animal.species) { 
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@s", animal.species]; 
} else { 
    cell.detailTextLabel.text = animal.species; 
} 
return cell; 
} 

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{ 

NSIndexPath *indexPath = [self.tableView indexPathForCell:sender]; 
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath]; 
// be somewhat generic here (slightly advanced usage) 
// we'll segue to ANY view controller that has a photographer @property 
if ([segue.identifier isEqualToString:@"newAnimal"]) { 
    NSLog(@"self.photodatabase"); 
    [(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext]; 
} else if ([segue.destinationViewController respondsToSelector:@selector(setAnimal:)]) { 
    // use performSelector:withObject: to send without compiler checking 
    // (which is acceptable here because we used introspection to be sure this is okay) 
    [segue.destinationViewController performSelector:@selector(setAnimal:) withObject:animal]; 
    NSLog(@"animal: %@ \r\n indexPath: %@", animal, indexPath); 
} 
} 

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{ 
return 30; 
} 

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView 
{ 
return nil; 
} 

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 
{  
NSLog(@"header for section called for section: %d", section); 
NSLog(@"fetchedResultsController sections: %@", self.fetchedResultsController.sections); 
CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30); 
UIView *header = [[UIView alloc] initWithFrame:headerRect]; 
UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)]; 
if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) { 
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0]; 
} else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) { 
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1]; 
} else { 
    headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2]; 
} 
headerTitleLabel.textColor = [UIColor whiteColor]; 
headerTitleLabel.font = [UIFont fontWithName:@"AmericanTypewriter" size:20]; 
headerTitleLabel.backgroundColor = [UIColor clearColor]; 
headerTitleLabel.alpha = 0.8; 
[header addSubview:headerTitleLabel]; 
return header; 
} 

답변

3

너무 많은 코드 : 여기

CoreDataTableViewController.m 
#pragma mark - Fetching 

- (void)performFetch 
{ 
self.debug = 1; 
if (self.fetchedResultsController) { 
    if (self.fetchedResultsController.fetchRequest.predicate) { 
     if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate); 
    } else { 
     if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName); 
    } 
    NSError *error; 
    [self.fetchedResultsController performFetch:&error]; 
    if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]); 
} else { 
    if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); 
} 
[self.tableView reloadData]; 
} 

- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc 
{ 
NSFetchedResultsController *oldfrc = _fetchedResultsController; 
if (newfrc != oldfrc) { 
    _fetchedResultsController = newfrc; 
    newfrc.delegate = self; 
    if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) { 
     self.title = newfrc.fetchRequest.entity.name; 
    } 
    if (newfrc) { 
     if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set"); 
     [self performFetch]; 
    } else { 
     if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd)); 
     [self.tableView reloadData]; 
    } 
} 
} 

#pragma mark - UITableViewDataSource 

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
if (self.debug) NSLog(@"fetchedResultsController returns %d sections", [[self.fetchedResultsController sections] count]); 
return [[self.fetchedResultsController sections] count]; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects]; 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
{ 
    return [[[self.fetchedResultsController sections] objectAtIndex:section] name]; 
} 

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex: (NSInteger)index 
{ 
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index]; 
} 

    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView 
{ 
return [self.fetchedResultsController sectionIndexTitles]; 
} 

#pragma mark - NSFetchedResultsControllerDelegate 

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{ 
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) 
{ 
    [self.tableView beginUpdates]; 
    self.beganUpdates = YES; 
} 
} 

- (void)controller:(NSFetchedResultsController *)controller 
    didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo 
     atIndex:(NSUInteger)sectionIndex 
forChangeType:(NSFetchedResultsChangeType)type 
{ 
    if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) 
{ 
    switch(type) 
    { 
     case NSFetchedResultsChangeInsert: 
      [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

     case NSFetchedResultsChangeDelete: 
      [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
    } 
} 
} 


- (void)controller:(NSFetchedResultsController *)controller 
    didChangeObject:(id)anObject 
    atIndexPath:(NSIndexPath *)indexPath 
forChangeType:(NSFetchedResultsChangeType)type 
    newIndexPath:(NSIndexPath *)newIndexPath 
{  
if(self.debug) NSLog(@"controller didChangeObject: %@", anObject); 
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) 
{ 
NSLog(@"#########Controller did change type: %d", type);  
switch(type) 
    { 
     case NSFetchedResultsChangeInsert: 
      [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

     case NSFetchedResultsChangeDelete: 
      [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

     case NSFetchedResultsChangeUpdate: 
      [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

     case NSFetchedResultsChangeMove: 
      [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
    } 
} 
} 

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{ 
if (self.beganUpdates) [self.tableView endUpdates]; 
if (self.debug) NSLog(@"controller Did Change Content"); 
} 

- (void)endSuspensionOfUpdatesDueToContextChanges 
{ 
_suspendAutomaticTrackingOfChangesInManagedObjectContext = NO; 
} 

- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend 
{ 
if (suspend) { 
    _suspendAutomaticTrackingOfChangesInManagedObjectContext = YES; 
} else { 
    [self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0]; 
} 
} 

@end 

그리고 여기 내 특정 뷰 컨트롤러 내가 그것에서 서브 클래스의 내보기 컨트롤러의 슈퍼 클래스입니다.

그러나 빠른 검사에서 MOC 제약 조건을 위반하는 것처럼 보입니다. 특히, 자체 스레드가 아닌 직접 상위 컨텍스트에 액세스하고 있습니다.

일반적으로 새 스레드를 시작한 다음 해당 스레드에서 MOC를 만들고 부모를 문서의 MOC로 만듭니다. 그런 다음 물건을 만들고 새로운 MOC에서 저장을 호출하십시오. 그런 다음 업데이트를 처리해야하는 부모에게 알립니다.

관련 문제