【问题标题】:NSFetchedResultsController and NSOrderedSet relationshipsNSFetchedResultsController 和 NSOrderedSet 关系
【发布时间】:2012-02-04 05:13:16
【问题描述】:

我在使用 NSFetchedResultsController 和 iOS 5 中可用的新 NSOrderedSet 关系时遇到问题(老实说是理解问题)。

我有以下数据模型(好吧,我真正的不是抽屉和袜子!)但这只是一个简单的例子:

Drawer 和 Sock 都是 Core Data 模型/存储中的 NSManagedObjects。在Drawer 上,socks 关系是与Sock有序对多 关系。这个想法是袜子按特定顺序放在抽屉里。在Sock 上,drawer 关系与socks 关系相反。

在 UIViewController 中,我正在绘制基于这些实体的 UITableView。我正在使用NSFetchedResultsController 喂桌子。

- (NSFetchedResultsController *)fetchedResultsController1 {
    if (_fetchedResultsController1 != nil) {
        return _fetchedResultsController1;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Sock" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"drawer.socks" ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

    self.fetchedResultsController1 = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:@"SocksCache"];
    self.fetchedResultsController1.delegate = self;

    return _fetchedResultsController1;    
}

当我运行它时,我得到以下错误:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'

这对我来说很有意义,因为关系是 NSOrderedSet,而不是单个实体进行比较以进行排序。

我想要实现的是Sockssocks 关系中指定的顺序出现在UITableView 中。我真的不想有一个排序顺序,但是NSFetchedResultsController,这是一个很棒的组件,它坚持必须有一个。我如何告诉它在抽屉实体上使用袜子订单。我根本不希望表格显示抽屉实体。

注意:我仅在 iOS5 应用程序中使用它,因此可以使用有序关系。

任何可以为我提供任何方向的人,将不胜感激。感谢您的宝贵时间。

编辑: 所以显示袜子的表格视图只对一个抽屉这样做。我只想让表格视图遵守 socks 关系包含的顺序。我不确定要设置什么排序标准来确保发生这种情况。

【问题讨论】:

  • 达米安,你似乎已经解决了这个问题。当您说“按照socks 关系中指定的顺序”时 - 您如何指定关系的顺序?在编辑器中,我只能看到一个复选框(已排序或未排序),但无法控制排序标准。
  • @jhabbott。当您指定有序关系(使用复选框)时,该关系由 NSOrderedSet 表示。然后你可以使用通常的有序集合方法来控制顺序。
  • 好的,现在我开始使用生成的insertObject:inSocksAtIndex: 方法,但是我得到了一个无法识别的选择器异常。我认为托管对象上下文会在运行时添加它?
  • @jhabbott 每个人都是如此,但这似乎是 Apple 长期存在的错误。在stackoverflow.com/questions/7385439/… 中查看'LeeIII' 的答案你必须自己实现这样的方法,直到错误被修复(并且它已经存在多年了!)
  • 可以理解。有序关系在 Core Data 中承诺并交付的很少。我真的希望他们工作得更好。

标签: iphone objective-c ios core-data ios5


【解决方案1】:

为了让 adonoho 的答案更加清晰(感谢队友),这帮助我解决了这个问题 - 而不是试图将多对多关系指定为我也无法工作的任何排序键,指定属于-to 关系在谓词中选择您想要在 fetched results 控制器中的实体,并指定属于关系作为排序键。

这满足了将结果保存在 NSFetchedResultsController 中的主要目标,并尊重多对多关系中的顺序。

在这个特定的例子中(按抽屉取袜子):

// socks belong-to a single drawer, sort by drawer specified sock order
fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"drawer" ascending:YES]];

// drawer has-many socks, select the socks in the given drawer
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"drawer = %@", drawer];

本质上,这使用给定的抽屉来指定哪些袜子应该进入 NSFetchedResultsController,以及由一对多关系指定的排序。

通过核心数据 SQL 调试查看生成的 SQL(从我的示例中使用不同的实体名称进行注释):

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, ...fields..., t0.ZDRAWER, t0.Z_FOK_DRAWER FROM ZSOCKS t0 WHERE  t0.ZDRAWER = ?  ORDER BY  t0.Z_FOK_DRAWER

您可以使用 Z_FOK_DRAWER 列查看 SQL 排序,Core Data 将其用于该袜子的位置。

【讨论】:

  • 我很高兴看到这行得通!...但后来我发现它在使用 NSFetchedResultsController 时会导致错误 - 更改关系成员会导致崩溃:CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[_NSFaultingMutableSet compare:]: unrecognized selector sent to instance 0x283a2a660 with userInfo (null)跨度>
【解决方案2】:

正如我刚刚回答 here 一样,我更喜欢通过类别向我的 NSManagedObject 添加一个新属性。

只需添加一个方法:

- (NSUInteger)indexInDrawerSocks
{
    NSUInteger index = [self.drawer.socks indexOfObject:self];
    return index;
}

然后在你的 NSFetchedResultsController 中,使用排序描述符:

fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"indexInDrawerSocks" ascending:YES]];

【讨论】:

  • 这似乎不适用于 SQL 支持的设置。我试过这个,获取请求抛出异常:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath indexInBookChapters not found in entity <NSSQLEntity TheObject id=2>'
【解决方案3】:

我在寻找 OP 提出的确切问题的答案时发现了这个线程。

我从来没有发现任何在tableView 中显示此类数据而不添加额外排序字段的示例。当然,添加排序字段几乎消除了使用有序关系的任何好处。因此,鉴于我得到了这个工作,我认为如果我在这里发布我的代码可能会对其他有同样问题的人有所帮助。结果证明它非常简单(比使用额外的排序字段简单得多)并且具有明显良好的性能。有些人可能没有意识到(包括我,最初)是“有序,多对多”关系属性的类型(NSOrderedSet)有一个获取objectAtIndex的方法,而NSMUtableOrderedSet有方法用于插入和删除objectAtIndex

正如一些海报所建议的那样,我避免使用NSFetchedResultsController。我没有使用数组,没有用于排序的附加属性,也没有使用谓词。我的代码处理tableView,其中有一个行程实体和许多地点实体,itinerary.places 是“有序、对多”关系字段。我启用了编辑/重新排序,但没有删除单元格。 moveRowAtIndexPath 方法显示了我如何使用重新排序来更新数据库,尽管为了更好地封装,我可能应该将数据库重新排序移动到我的位置管理对象的类别文件中。这是整个TableViewController.m

//
//  ItineraryTVC.m
//  Vacations
//
//  Created by Peter Polash on 8/31/12.
//  Copyright (c) 2012 Peter Polash. All rights reserved.
//

#import "ItineraryTVC.h"
#import "AppDelegate.h"
#import "Place+PlaceCat.h"
#import "PhotosInVacationPlaceTVC.h"


@interface ItineraryTVC ()

@end

@implementation ItineraryTVC

#define DBG_ITIN YES

@synthesize itinerary ;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.navigationItem.rightBarButtonItem = self.editButtonItem ;
}

- (void) viewWillAppear:(BOOL)animated
{

    [super viewWillAppear:animated] ;

    UIManagedDocument *doc = UIAppDelegate.vacationDoc;

    [doc.managedObjectContext performBlock:^
     {   // do this in the context's thread (should be the same as the main thread, but this made it work)

         // get the single itinerary for this document

         self.itinerary = [Itinerary setupItinerary: doc ] ;
         [self.tableView reloadData] ;
     }];

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown );
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    return [self.itinerary.places count ];
}


- (UITableViewCell *) tableView: (UITableView *) tableView
          cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
    static NSString *CellIdentifier = @"Itinerary Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier ];


    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault   reuseIdentifier: CellIdentifier];
    }

    Place *place = [self.itinerary.places objectAtIndex:indexPath.row];

    cell.textLabel.text       = place.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d photos", [place.photos count]];

    return cell;
}


#pragma mark - Table view delegate


- (BOOL)    tableView: (UITableView *) tableView
canMoveRowAtIndexPath:( NSIndexPath *) indexPath
{
    return YES;
}

-(BOOL)     tableView: (UITableView *) tableView
canEditRowAtIndexPath: (NSIndexPath *) indexPath
{
    return YES ;
}

-(void)  tableView: (UITableView *) tableView
moveRowAtIndexPath: (NSIndexPath *) sourceIndexPath
       toIndexPath: (NSIndexPath *) destinationIndexPath
{
    UIManagedDocument * doc = UIAppDelegate.vacationDoc ;

    [doc.managedObjectContext performBlock:^
    { // perform in the context's thread 

        // itinerary.places is the "ordered, to-many" relationship attribitute pointing to all places in itinerary
        NSMutableOrderedSet * places = [ self.itinerary.places  mutableCopy ] ;
        Place *place                 = [ places objectAtIndex:  sourceIndexPath.row] ;

        [places removeObjectAtIndex: sourceIndexPath.row ] ;
        [places insertObject: place   atIndex: destinationIndexPath.row ] ;

        self.itinerary.places = places ;

        [doc saveToURL: doc.fileURL   forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) {
            if ( !success ) NSLog(@"Error saving file after reorder, startPos=%d, endPos=%d", sourceIndexPath.row, destinationIndexPath.row) ;
        }];
    }];

}

- (UITableViewCellEditingStyle) tableView: (UITableView *) tableView
            editingStyleForRowAtIndexPath: (NSIndexPath *) indexPath
{
    return ( UITableViewCellEditingStyleNone ) ;
}

- (void) prepareForSegue:(UIStoryboardSegue *) segue   sender: (id) sender
{
    NSIndexPath *indexPath = [self.tableView    indexPathForCell: sender] ;
    PhotosInVacationPlaceTVC  * photosInVacationPlaceTVC = segue.destinationViewController ;

    Place *place = [self.itinerary.places objectAtIndex:indexPath.row ];

    photosInVacationPlaceTVC.vacationPlace        = place ;
    photosInVacationPlaceTVC.navigationItem.title = place.name ;

    UIBarButtonItem *backButton =
    [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backButton;

}


@end

【讨论】:

    【解决方案4】:

    你可以给 Sock 一个 index 属性并按它们排序袜子:

    sock01.index = [sock01.drawer.socks indexOfObject:sock01];
    

    【讨论】:

    【解决方案5】:

    达米恩,

    你应该让你的NSFetchRequest 使用有序集的数组形式。它会运行良好。你的控制器需要一个属性来排序。因此,您也需要指定它。

    安德鲁

    【讨论】:

    • 你好安德鲁。非常感谢您花时间回答问题。我想我明白你在说什么,我知道我可以从有序集中得到一个 NSArray 。我只是不确定如何将它输入到 NSFetchRequest 中。这点我可能想多了。你能按正确的方向刺激我吗?
    • Damien,你只需要一个简单的谓词。例如:@"self in %@", orderedSet.array。排序描述符应该同样简单——选择一个不改变排序顺序的属性。安德鲁
    • 嗨,安德鲁,感谢一百万的帮助。那行得通。这是排序顺序,实际上并没有改变,这是我想念的一点。实体中的某些东西可能很难找到这样的属性,所以我在想有时我可能必须有一个不会改变排序顺序的虚拟属性。看起来 NSFetchedResultsController 可能需要跟上有序的关系
    • 有序关系通常代表用户定义的偏好,通常不会在对象本身的属性中表示。如果数据已经具有适合排序的属性,他为什么要使用有序关系尚不清楚。他可以为此目的专门添加一个 displayOrder 属性,但这需要在关系中的所有对象中维护该值,此时使用有序关系可能不太适合。
    • 好的,我了解 CoreData 的工作原理,是的,获取返回插入顺序。我使用字段没有问题,但是 1. 这不是这个问题的主题,其次这是旧的解决方案,在这种情况下你可以使用正常的关系(对 iCloud 也更好)。我猜(和我自己一样)大多数人评估了他们如何找到与 UITableView 的绑定。结果是NSFetchedResultsController 恕我直言不合适。因此我认为目前(iOS5.0)这个答案是错误的,第二个是正确的。希望 Apple 会在下一个版本中改进这一点。
    【解决方案6】:

    据我了解此功能,它允许在每个Drawer 中订购Socks。正如 Apple 在documentation 中所写:

    只有在关系具有内在顺序时才应该使用它们 对其自身的表示至关重要——例如配方中的步骤。

    这意味着您无法使用排序关系获取所有Socks。排序后的Socks 将仅在每个Drawer 对象中可用。

    【讨论】:

    • 您好,感谢您的回复。抱歉,我没有说清楚。该表代表一个抽屉,因此我只希望该表显示该特定抽屉中的袜子。我可以这样做,但我不知道如何设置排序标准,以便根据父实体(抽屉的)袜子排序关系以正确的顺序显示袜子。我会更新我的问题。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多