NSFetchedResultsController numberOfObjects 在设备上运行时返回附加(重复)

发布于 2024-11-06 11:33:16 字数 4983 浏览 0 评论 0原文

我已经使用 NSFetchedResultsController 实现了 UITableView 以从 sqllite 数据库加载数据。我将数据库从设备下载到模拟器,因此两者是相同的。

我观察到奇怪的行为,当在模拟器上运行时,表格视图填充了正确数量的单元格(在初始基本情况下:一个单元格);但是,当我在设备上运行相同的代码时,numberOfObjects 返回 2,并且表格视图显示两个(相同/重复)单元格。

当我检查 sqllite 文件时,其中确实只有 1 个对象/行...

我按照示例代码进行实现,并且没有做任何特殊的事情。

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSInteger count = [[fetchedResultsController sections] count];

    if (count == 0) {
        count = 1;
    }

    return count;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger numberOfRows = 0;

    if ([[fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
        numberOfRows = [sectionInfo numberOfObjects];
    }

    NSLog(@"SwoopListTableViewController::numberOfRowsInSection - numberOfRows:%d", numberOfRows);
    return numberOfRows;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Dequeue or if necessary create a SwoopTableViewCell, then set its Swoop to the Swoop for the current row.            
    static NSString *SwoopCellIdentifier = @"SwoopCellIdentifier";

    SwoopTableViewCell *SwoopCell = (SwoopTableViewCell *)[tableView dequeueReusableCellWithIdentifier:SwoopCellIdentifier];
    if (SwoopCell == nil) {
        SwoopCell = [[[SwoopTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SwoopCellIdentifier] autorelease];
        SwoopCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    [self configureCell:SwoopCell atIndexPath:indexPath];
    return SwoopCell;
}

- (NSFetchedResultsController *)fetchedResultsController {
    // Set up the fetched results controller if needed.
    NSLog(@"SwoopListTableViewController::fetchedResultsController - started");

    if (fetchedResultsController == nil) {

        if (managedObjectContext == nil) 
        { 
            managedObjectContext = [(SwoopAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; 
            NSLog(@"SwoopListTableViewController::fetchedResultsController - set managedObjectContext");
        }

        // Create the fetch request for the entity.
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Swoop" inManagedObjectContext:managedObjectContext];
        [fetchRequest setEntity:entity];

        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];

        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
        aFetchedResultsController.delegate = self;
        self.fetchedResultsController = aFetchedResultsController;

        [aFetchedResultsController release];
        [fetchRequest release];
        [sortDescriptor release];
        [sortDescriptors release];
    }

    return fetchedResultsController;
}  

很困惑为什么相同的代码 &相同的数据库在模拟器上(按预期工作)与设备上(3GS - 显示重复的表格单元格)会表现出不同的行为。任何人都可以帮助/解释/提供一些关于我应该看什么的见解吗?

非常感谢, Eric

** 编辑 1: ** 我对 NSCoreData 和 NSFetchedResultsController 进行了更多调试。看来 fetchRequest 确实从 ManagedContext 返回了重复的对象。这是代码和相应的控制台输出:

控制器代码:

- (void)viewDidLoad {   
    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }       
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:0];
    NSLog(@"SwoopListTableViewController::viewDidLoad - sectionInfo objects:%@", [sectionInfo objects] );        
}

控制台输出:

2011-05-14 17:54:53.388 Swoop[1471:307] SwoopListTableViewController::viewDidLoad - sectionInfo objects:(
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x188180> (entity: Swoop; id: 0x143a60 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p1> ; data: <fault>)"
)

我发现很奇怪的是,sectionInfo中的前两个对象都具有相同的内存地址“0x187d60”,并且都具有相同的x-coredata“路径”:“ //A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2"...有人可以解释这是什么意思,或者可能发生了什么?

谢谢, 埃里克

I have implemented a UITableView with the NSFetchedResultsController to load data from an sqllite database. I downloaded the database from the device to the simulator, so it is the same for both.

I observe the strange behavior that, when run on the simulator, the tableview is populated with the correct number of cells (in the initial base-case: one cell); however when I run the same code on the device, numberOfObjects returns 2, and the tableview displays two (identical/repeated) cells.

When I inspect the sqllite file there is indeed just 1 object/row in it...

I followed the example code for my implementation and am not doing anything special.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    NSInteger count = [[fetchedResultsController sections] count];

    if (count == 0) {
        count = 1;
    }

    return count;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger numberOfRows = 0;

    if ([[fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
        numberOfRows = [sectionInfo numberOfObjects];
    }

    NSLog(@"SwoopListTableViewController::numberOfRowsInSection - numberOfRows:%d", numberOfRows);
    return numberOfRows;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Dequeue or if necessary create a SwoopTableViewCell, then set its Swoop to the Swoop for the current row.            
    static NSString *SwoopCellIdentifier = @"SwoopCellIdentifier";

    SwoopTableViewCell *SwoopCell = (SwoopTableViewCell *)[tableView dequeueReusableCellWithIdentifier:SwoopCellIdentifier];
    if (SwoopCell == nil) {
        SwoopCell = [[[SwoopTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SwoopCellIdentifier] autorelease];
        SwoopCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    [self configureCell:SwoopCell atIndexPath:indexPath];
    return SwoopCell;
}

and

- (NSFetchedResultsController *)fetchedResultsController {
    // Set up the fetched results controller if needed.
    NSLog(@"SwoopListTableViewController::fetchedResultsController - started");

    if (fetchedResultsController == nil) {

        if (managedObjectContext == nil) 
        { 
            managedObjectContext = [(SwoopAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; 
            NSLog(@"SwoopListTableViewController::fetchedResultsController - set managedObjectContext");
        }

        // Create the fetch request for the entity.
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Swoop" inManagedObjectContext:managedObjectContext];
        [fetchRequest setEntity:entity];

        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];

        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
        aFetchedResultsController.delegate = self;
        self.fetchedResultsController = aFetchedResultsController;

        [aFetchedResultsController release];
        [fetchRequest release];
        [sortDescriptor release];
        [sortDescriptors release];
    }

    return fetchedResultsController;
}  

I'm puzzled why the same code & same database would exhibit different behavior on the simulator (works as expected) vs. the device (3GS - duplicate table cells displayed). Can anyone help / explain / lend some insight as to what I should be looking at?

Many thanks,
Eric

** EDIT 1: ** I have done a bit more debugging into NSCoreData and NSFetchedResultsController. It seems that the fetchRequest is indeed returning a duplicate objects from the managedContext. Here is a the code and the corresponding console output:

Controller Code:

- (void)viewDidLoad {   
    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }       
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:0];
    NSLog(@"SwoopListTableViewController::viewDidLoad - sectionInfo objects:%@", [sectionInfo objects] );        
}

Console Output:

2011-05-14 17:54:53.388 Swoop[1471:307] SwoopListTableViewController::viewDidLoad - sectionInfo objects:(
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x188180> (entity: Swoop; id: 0x143a60 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p1> ; data: <fault>)"
)

I find it strange that the first two objects in sectionInfo both have the same memory address "0x187d60" and both have the same x-coredata 'path': "//A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2"... can someone explain what this means, or what might be going on?

Thanks,
Eric

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

泪痕残 2024-11-13 11:33:17

经过更多阅读和挖掘 NSCoreData 后,问题似乎是由于设备上管理内存的方式的特殊性造成的(我假设相同的内存约束/管理将应用于模拟器,但我认为存在差异两者之间)——具体来说,如果在内存不足警告时卸载视图,则再次重新加载视图时将执行提取,这可能会导致重复条目。

我发现以下链接中描述的问题/解决方案解决了我遇到的问题:

使用 NSFetchedResultsController 复制 NSManagedObject

另一方面,了解如何通过将此参数传递给应用程序来启用不同级别的 NSCoreData 日志记录也很有帮助:

-com.apple.CoreData.SQLDebug 1

在此处阅读更多内容:

从 Apple Developer 库,请参阅“故障排除”核心数据: 调试获取 "

要通过 xcode 添加参数,请参阅本页上的“为命令行程序提供启动参数”http://www.meandmark.com/xcodetips.html

After more reading and digging around with NSCoreData, it seems the issue is due to a peculiarity of how memory is managed on the device (I would have assumed the same memory constraints/management would be applied to the simulator, but I guess there are differences between the two) -- specifically, if a view is unloaded on a low-memory warning, a fetch will be performed when the view is once again reloaded, which results in possible duplicate entries.

I found that the problem/solution described at the following link solves the problem I was experiencing:

Duplicate NSManagedObject with NSFetchedResultsController

On another note, it was also helpful to learn about being able to enable different levels of NSCoreData logging by passing this argument to the application:

-com.apple.CoreData.SQLDebug 1

Read more here:

From the Apple Developer library, see "Troubleshooting Core Data: Debugging Fetching "

To add an argument via xcode, see "Supplying Launch Arguments for Command-Line Programs" on this page http://www.meandmark.com/xcodetips.html

万人眼中万个我 2024-11-13 11:33:17

我也一直忙于(SQLite3-)数据库。您是否尝试过清洁设备(使用“产品”->“清洁”,同时选择在 iOS 设备上运行)?构建和运行时,如果存在数据库,则不会更新...因此,您可能正在使用旧数据库!


A second thing programmers tend to forget, is that the database in your bundle isn't editable! So first, you have to copy your database from your bundle (the list of the items on the left in Xcode) to a path on your device/simulator.

NSError *error;
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"database.sqlite3"];
NSString *writableDBPath = [docsDir stringByAppendingPathComponent:@"fdatabase.sqlite3"];
NSFileManager *filemgr = [NSFileManager defaultManager];
BOOL succes = [filemgr fileExistsAtPath:writableDBPath];

if (!succes)
{
    //writable database does not exist, so copy default nonwritable from bundle
    succes = [filemgr copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
    //NSLog(@"Database file copied from bundle to %@", dbPath);
    if (!succes) 
        NSLog(@"Failed to create writable database file with message '%@'.", [error localizedDescription]);
    //return NO;
}

databasePath = [[NSString alloc] initWithFormat:@"%@", writableDBPath];

我在这里创建了两条路径。第一个是您的defaultPath。这是您应该包含在您的项目中的。您的项目中包含的路径不可编辑,因此我创建了 writableDBPath。其余代码在注释中进行了解释,因此它应该是不言自明的,但如果不是,请给我回信。

I've been busy on (SQLite3-)databases as well. Have you tried cleaning the device (using Product -> Clean, while iOS Device selected to run on)? When building and running, if there is a database existing, it won't get renewed... So, you might be using an old database!


A second thing programmers tend to forget, is that the database in your bundle isn't editable! So first, you have to copy your database from your bundle (the list of the items on the left in Xcode) to a path on your device/simulator.

NSError *error;
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"database.sqlite3"];
NSString *writableDBPath = [docsDir stringByAppendingPathComponent:@"fdatabase.sqlite3"];
NSFileManager *filemgr = [NSFileManager defaultManager];
BOOL succes = [filemgr fileExistsAtPath:writableDBPath];

if (!succes)
{
    //writable database does not exist, so copy default nonwritable from bundle
    succes = [filemgr copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
    //NSLog(@"Database file copied from bundle to %@", dbPath);
    if (!succes) 
        NSLog(@"Failed to create writable database file with message '%@'.", [error localizedDescription]);
    //return NO;
}

databasePath = [[NSString alloc] initWithFormat:@"%@", writableDBPath];

I created two paths here. The first is your defaultPath. This is the one you should have included in your project. The one included in your project isn't editable, so I created the writableDBPath. The rest of the code is explained in comments, so it should be self-explaining, but write me back if it isn't.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文