核心数据似乎是获取空对象

时间:2015-02-10 00:29:41

标签: ios objective-c xcode sqlite core-data

在我的iOS应用程序中,我试图通过核心数据将值保存到一个视图中的sqlite数据库,然后在另一个视图中检索。查看数据库,保存看起来工作正常,对象在数据库中按预期方式运行。如果我然后转到我的集合视图,结果会正确显示。但是,如果我是相当的应用程序并重新打开,检索时集合视图显示正确的单元格数(行)但所有都是空的??? 获取计数总是返回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行,此时就是那里的所有内容

以下是我的集合视图数据源方法,以防有:

#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在我的调用中指出错误来计算fetch,我把这段代码移到了适当的位置,它确实返回了正确数量的对象

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

进一步编辑: 正如所建议的那样,我在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,因为它未通过该条件。但是,如果您再添加一个新的播放器,然后返回到同一会话中的viewcontroller视图,NSLog会将播放器图像报告为NSconcreteMutableData的类型,并正确填充单元格以及正确数量的空白单元格。如果您关闭应用程序并关闭然后返回,它只返回正确数量的空单元格。

编辑:添加新播放器代码(警告:潜在危险代码!!;)

- (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和profile对象只是从模型中各个实体创建的那些类的实例。他们的文件基本上只包含每个Entity属性的属性

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

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

编辑: 我已经向我的app委托添加了一个获取请求,并在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);

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

*非常讨厌

3 个答案:

答案 0 :(得分:0)

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

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

修改

我相信你的问题可能在于保存对象的代码。正如评论中所述,如果不立即使用初始化,则无法使用alloc。对于NSManagedObjects(和子类),指定的初始化程序为initWithEntity:insertIntoManagedObjectContext:。但是,我猜你不希望self.playerself.profile添加到数据库AS WELL AS 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();
        }
    }
}

答案 1 :(得分:0)

Craigen的2015年2月10日的评论帮助了我(Core Data noob)在看到单元格中的空值或只是缺少行的许多无效时间之后,并想知道如何从一个看起来正确但从未与我匹配的fetch中获取行计数NSFetchedResultsSectionInfo numberOfObjects。将NSFetchedResultsController cacheName更改为nil突然将我丢失的行放入tableview中,这一切终于有意义了。

答案 2 :(得分:-2)

我最终找到了解决这个问题的方法,而且Player类没有正确地初始化属性等。所以当我创建这个类的实例时,属性没有被初始化,所以是null 。我在Player.m文件中添加了一个初始化方法,并删除了合成语句,一切都开始了。我将发布我后来使用的方法。

非常感谢pbasdf对此提供的所有帮助,没有这些帮助我就不会接近答案 - 这是对社区的一种赞誉。