核心数据似乎是空的 objects

Core data seems to fetch empty objects

在我的 iOS 应用程序中,我试图通过核心数据将值保存到一个视图中的 sqlite 数据库,然后在另一个视图中检索。查看数据库,保存看起来工作正常,objects 按预期在数据库中。如果我然后转到我的 collection 视图,结果将正确显示。但是,如果我关闭应用程序并重新打开,检索 collection 视图时会显示正确数量的单元格(行),但所有单元格都是空的??? 获取计数总是 returns 0 但设置了命令行参数以查看 sql,这表明它获取了正确的行数。我的获取请求如下:

- (NSFetchedResultsController *)fetchedResultsController {

if (_fetchedResultsController != nil) {
    return _fetchedResultsController;
}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"Player" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];

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

[fetchRequest setFetchBatchSize:20];

NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:context sectionNameKeyPath:nil
                                               cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
NSLog(@"fetch count %@",[NSString stringWithFormat:@"%d",[theFetchedResultsController.fetchedObjects count]]);
_fetchedResultsController.delegate = self;

return _fetchedResultsController;

}

这会通过命令行 args 工具为 sql 生成以下日志:

CoreData: sql: SELECT 0, t0.Z_PK FROM ZPLAYER t0 ORDER BY t0.ZSURNAME DESC
2015-02-09 23:56:39.522 teamPicker[290:20878] CoreData: annotation: sql    connection fetch time: 0.0013s
2015-02-09 23:56:39.523 teamPicker[290:20878] CoreData: annotation: total   fetch execution time: 0.0020s for 4 rows.
2015-02-09 23:56:39.562 teamPicker[290:20878] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZEMAIL, t0.ZFORENAME, t0.ZMOBNUM, t0.ZMUGSHOT, t0.ZSURNAME, t0.ZTWITTER, t0.ZPROFILE FROM ZPLAYER t0 WHERE  t0.Z_PK IN  (?,?,?,?)  ORDER BY t0.ZSURNAME DESC LIMIT 20
2015-02-09 23:56:39.711 teamPicker[290:20878] CoreData: annotation: sql   connection fetch time: 0.1408s
2015-02-09 23:56:39.711 teamPicker[290:20878] CoreData: annotation: total fetch execution time: 0.1495s for 4 rows.

如您所见,似乎 运行 有两个我觉得很奇怪的查询,但作为一个菜鸟,我不知道这是否只是它的工作原理。它似乎暗示的是,尽管获取计数报告为 0,但它已经获取了 4 行,此时就是所有这些了

以下是我的 collection 查看数据源方法,以备不时之需:

#pragma mark - UICollectionView Datasource
// 1
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
id  sectionInfo =
[[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
// 2
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 1;
}
// 3
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Player *info = [_fetchedResultsController objectAtIndexPath:indexPath];
playerPhotoCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"playerCell" forIndexPath:indexPath];
cell.playerImage.image = [UIImage imageWithData:info.mugshot];
cell.playerLabel.text = info.forename;
cell.backgroundColor = [UIColor whiteColor];
return cell;
}

我可能犯了一些明显的基本错误,但如果我能发现它们或找到任何答案,我就该死了。如果有人能帮助我指明正确的方向,我将不胜感激。感谢阅读

编辑:感谢 pbasdf 指出了我调用计算获取次数的错误,我将这段代码移到了适当的位置,它确实返回了正确数量的 objects

正如后来所承诺的,我的 playerPhotoCell 只是一个脚本,在 h 文件中有两个属性,用作图像视图的出口和我添加到情节提要中的单元格的标签。 .m 文件中没有方法,因为我认为我的 viewController 代码可以简单地引用插座并设置它们的值 - 事实上,如果您在与保存的相同 session 中查看,这似乎有效由于单元格已正确填充 - 当应用程序关闭并重新加载时会出现问题 - 加载了正确数量的单元格,但所有单元格都只有白色背景且没有信息

进一步编辑: 正如建议的那样,我向 cellForItemAtIndexPath 添加了一个简单的检查,以查看值是否被正确填充,而它们不是,它们实际上为空。更新方法如下:

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    Player *info = [_fetchedResultsController objectAtIndexPath:indexPath];
    playerPhotoCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@"playerCell" forIndexPath:indexPath];
    cell.playerImage.image = [UIImage imageWithData:info.mugshot];
    cell.playerLabel.text = info.forename;
    if(info.mugshot){
        NSLog(@"Player Image is of type %@", NSStringFromClass([info.mugshot class]));
    }
    cell.backgroundColor = [UIColor whiteColor];
    return cell;
}

info.mugshot 的 NSLog 未被调用,因为它不满足条件。但是,如果您然后去添加一个新播放器,然后返回到相同 session 中的 viewcontroller 视图,NSLog 报告播放器图像为 NSconcreteMutableData 类型并正确填充单元格具有正确数量的空白单元格。如果您关闭应用程序并关闭然后返回,它只是 returns 正确数量的空单元格。

编辑:添加新的玩家代码(警告:有潜在危险的代码!!;)

- (IBAction)saveBtn:(id)sender {
    self.player = [Player alloc];
    self.player.mugshot = UIImageJPEGRepresentation(_chosenImage, 1);
    self.player.forename = fName.text;
    self.player.surname = sName.text;
    self.player.email = email.text;
    self.player.mobNum = [NSNumber numberWithInteger:[mobNum.text integerValue]];
    if(twitHandle){
        self.player.twitter = twitHandle.text;
    }

    self.profile = [Profile alloc];
    self.profile.position = position.text;
    self.profile.type = _pressedBtnName;
    self.profile.workRate = [NSNumber numberWithInt:(wrRating) ];
    self.profile.stamina = [NSNumber numberWithInteger:(stRating)];
    self.profile.skill = [NSNumber numberWithInteger:(skRating) ];
    self.profile.finishing = [NSNumber numberWithInteger:(fiRating)];
    self.profile.passing = [NSNumber numberWithInteger:(paRating)];
    self.profile.strength = [NSNumber numberWithInteger:(strRating)];

    NSEntityDescription *playerEntityDescription = [NSEntityDescription entityForName:@"Player" inManagedObjectContext:context];
    NSManagedObject *newPlayer = [[NSManagedObject alloc] initWithEntity:playerEntityDescription insertIntoManagedObjectContext:context];
    [newPlayer setValue:self.player.forename forKey:@"forename"];
    [newPlayer setValue:self.player.surname forKey:@"surname"];
    [newPlayer setValue:self.player.email forKey:@"email"];
    [newPlayer setValue:self.player.mobNum forKey:@"mobNum"];
    [newPlayer setValue:self.player.twitter forKey:@"twitter"];
    [newPlayer setValue:self.player.mugshot forKey:@"mugshot"];

    NSEntityDescription *pattrEntityDescription = [NSEntityDescription entityForName:@"Profile" inManagedObjectContext:context];
    NSManagedObject *newPlayerAttr = [[NSManagedObject alloc] initWithEntity:pattrEntityDescription insertIntoManagedObjectContext:context];
    [newPlayerAttr setValue:self.profile.position forKey:@"position"];
    [newPlayerAttr setValue:self.profile.type forKey:@"type"];
    [newPlayerAttr setValue:self.profile.workRate forKey:@"workRate"];
    [newPlayerAttr setValue:self.profile.stamina forKey:@"stamina"];
    [newPlayerAttr setValue:self.profile.skill forKey:@"skill"];
    [newPlayerAttr setValue:self.profile.finishing forKey:@"finishing"];
    [newPlayerAttr setValue:self.profile.passing forKey:@"passing"];
    [newPlayerAttr setValue:self.profile.strength forKey:@"strength"];

    [newPlayer setValue:newPlayerAttr forKey:@"profile"];

在这一点上,我意识到我不应该像模型中定义的那样设置逆函数,但如果我没有通过以下行,我会得到一个错误 - 再次糟糕但只是想让一些东西工作

[newPlayerAttr setValue:newPlayer forKey:@"player"];

if (newPlayer.managedObjectContext != nil) {
        NSError *error = nil;
        if (![newPlayer.managedObjectContext save:&error]) {
           // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
           }
}

Player 和配置文件 objects 只是那些 class 的实例,它们是从模型中的各个实体创建的。他们的文件基本上只包含每个实体属性的 属性

查询sqlite db表明数据写入正确

有关信息,在我的 cellforitematindexpath 中,我放入了一个 NSLog 以输出 [_fetchedResultsController objectAtIndex:indexPath] 的类型,它随 Player 一起返回。因此,在此基础上,我不知道为什么创建 Player 实例信息并将其设置为此不起作用,但信息的属性为 null

编辑: 我已经向我的应用程序委托添加了一个获取请求,并在 didFinishLaunchingWithOptions 方法的末尾执行此操作,如下所示:

NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }
    NSLog(@"fetch count %@",[NSString stringWithFormat:@"%lu",(unsigned long)[self.fetchedResultsController.fetchedObjects count]]);
    Player *ken = [self.fetchedResultsController.fetchedObjects firstObject];
    NSLog(@"Surname %@",ken.surname);

获取的 object 计数报告 6 是正确的。但是,尝试首先将 fetchedObjects object 分配给名为 Ken 的 Player 实例的下一行会在下一行 'Surname (null) 上产生输出,这真的很棒*。无论如何,也许我的代码在那里是错误的,但似乎 object 根本没有被设置,更不用说属性了,因为我还对 ken object 进行了测试(未在代码片段中显示),例如if(ken){... 这是从未遇到过的。无论如何要在枕头上尖叫一会儿,也许会哭一下

*非常烦人

您需要告诉抓取结果控制器实际执行抓取。在 fetchedResultsController 代码中插入类似这样的内容,就在 return _fetchedResultsController; 行之前:

NSError *error = nil;
if (![_fetchedResultsController performFetch:&error]) {
    NSLog(@"Fetch error %@, %@", error, [error userInfo]);
    abort();
}

编辑

我相信您的问题可能在于保存对象的代码。如评论中所述,如果不立即使用初始化程序,则不能使用 alloc 。对于 NSManagedObjects(和子类),指定的初始化程序是 initWithEntity:insertIntoManagedObjectContext:。但我猜您不希望将 self.playerself.profile 添加到数据库以及 newPlayer 和 newPlayerProfile。所以我建议重写如下:

- (IBAction)saveBtn:(id)sender {
    NSEntityDescription *playerEntityDescription = [NSEntityDescription entityForName:@"Player" inManagedObjectContext:context];
    NSManagedObject *newPlayer = [[NSManagedObject alloc] initWithEntity:playerEntityDescription insertIntoManagedObjectContext:context];
    [newPlayer setValue:fName.text forKey:@"forename"];
    [newPlayer setValue:sName.text forKey:@"surname"];
    [newPlayer setValue:email.text forKey:@"email"];
    [newPlayer setValue:[NSNumber numberWithInteger:[mobNum.text integerValue]] forKey:@"mobNum"];
    if(twitHandle){
        [newPlayer setValue:twitHandle.text forKey:@"twitter"];
    }
    [newPlayer setValue:UIImageJPEGRepresentation(_chosenImage, 1) forKey:@"mugshot"];

    NSEntityDescription *pattrEntityDescription = [NSEntityDescription entityForName:@"Profile" inManagedObjectContext:context];
    NSManagedObject *newPlayerAttr = [[NSManagedObject alloc] initWithEntity:pattrEntityDescription insertIntoManagedObjectContext:context];
    [newPlayerAttr setValue:position.text forKey:@"position"];
    [newPlayerAttr setValue:_pressedBtnName forKey:@"type"];
    [newPlayerAttr setValue:[NSNumber numberWithInt:(wrRating)] forKey:@"workRate"];
    [newPlayerAttr setValue:[NSNumber numberWithInteger:(stRating)] forKey:@"stamina"];
    [newPlayerAttr setValue:[NSNumber numberWithInteger:(skRating)] forKey:@"skill"];
    [newPlayerAttr setValue:[NSNumber numberWithInteger:(fiRating)] forKey:@"finishing"];
    [newPlayerAttr setValue:[NSNumber numberWithInteger:(paRating)] forKey:@"passing"];
    [newPlayerAttr setValue:[NSNumber numberWithInteger:(strRating)] forKey:@"strength"];

    [newPlayer setValue:newPlayerAttr forKey:@"profile"];
    // You are right, you shouldn't need the next line
    // [newPlayerAttr setValue:newPlayer forKey:@"player"];

    // Not sure whether self.profile and self.player are used elsewhere,
    // So set them to the newly created objects:
    self.player = newPlayer;
    self.profile = newPlayerAttr;

    if (newPlayer.managedObjectContext != nil) {
        NSError *error = nil;
        if (![newPlayer.managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

我最终找到了这个问题的解决方案,就是播放器 class 没有正确初始化属性等。所以当我创建这个 class 的实例时,属性未初始化,因此为空。我在 Player.m 文件中添加了一个初始化方法,并删除了综合语句,一切都开始了。我会post我后面用的方法

非常感谢 pbasdf 在这方面提供的所有帮助,否则我将无法接近答案 - 这是社区的功劳。

Craigen 的 2015 年 2 月 10 日的评论帮助了我(Core Data 新手)在看到单元格中的空值或只是缺少行的许多小时无果之后,并且想知道我如何从看似正确但从来没有的提取中获得行数匹配我的 NSFetchedResultsSectionInfo numberOfObjects。将 NSFetchedResultsController cacheName 更改为 nil 突然将我丢失的行带入了表视图,这一切终于变得有意义了。