PDA

View Full Version : Counting Items in Core Data




sleaver
Aug 20, 2010, 02:51 PM
Hi

In my RSS Reader App when the application quits or enters the background I count the unread items in the entity that holds the storys with the following:

- (int)countUnreadItems {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"FeedItem" inManagedObjectContext:managedObjectContext]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@" read = 0"];
[request setPredicate:predicate];


NSError *err;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}

return count;
}
That is in a class but is called from the app delegate so the passed managed object context is:


- (NSManagedObjectContext *)managedObjectContext {

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

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
That works fine, however I have a FeedInfo entity (holding the name, summary URL of the feed etc) and obviously with a relationship between the two. In the FeedInfo entity I have a 'show' attribute which lets the user decide if they wish for the feed to display in the app.

So, say I have 5 feeds with 20 unread items each, the application badge shows 100 which is correct. However if the user hides two feeds it still shows 100 BUT I want it to show 60.

I have also tried:

- (int)countUnreadItems {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesSubentities:YES];
[request setEntity:[NSEntityDescription entityForName:@"FeedInfo" inManagedObjectContext:managedObjectContext]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@" (show = 1) AND (read = 0)"];
[request setPredicate:predicate];

NSError *err;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}

return count;
}

But get the following error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath read not found in entity <NSSQLEntity FeedInfo id=1>'

My question is, how can I adapt the above code to take into account the 'show' attribute with the least amount of overhead? Code examples would be good as I'm still trying to learn

My entity diagram is attached.



seepel
Aug 21, 2010, 10:49 AM
Since show is a property of FeedInfo and read is a property of FeedItem, if your NSPredicate entity is FeedInfo you want
FeedItem.read = 0

instead of
read = 0

sleaver
Aug 21, 2010, 05:38 PM
Thanks for the reply. So if I try the following should it work?

(FeedInfo.show = 1) AND (FeedItem.read = 0)

I ask because I did start to try it as per my first post but it gave the error that is also in that post.

I also read today that you can't actually have a predicate across entities as you can only request one at a time. So is that true or is that avoided by having the relationship and NSSet in the object for FeedInfo?

seepel
Aug 22, 2010, 10:54 AM
Thanks for the reply. So if I try the following should it work?

(FeedInfo.show = 1) AND (FeedItem.read = 0)

I ask because I did start to try it as per my first post but it gave the error that is also in that post.

I also read today that you can't actually have a predicate across entities as you can only request one at a time. So is that true or is that avoided by having the relationship and NSSet in the object for FeedInfo?

It depends on which entity you are using in your predicate. So if you use FeedItem

(FeedInfo.show = 1) AND (read = 0)

and FeedInfo you want

(show = 1) AND (FeedItem.read = 0)

when you set the entity of the NSPredicate you are saying "Find me items from this pile that look like this." It won't know to go look in another pile of items unless you tell it to.

sleaver
Aug 22, 2010, 02:58 PM
So I have changed my code to the following as per the reply. I'm using FeedInfo and so used FeedItem.read

- (int)countUnreadItems {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesSubentities:YES];
[request setEntity:[NSEntityDescription entityForName:@"FeedInfo" inManagedObjectContext:managedObjectContext]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@" (show = 1) AND (FeedItem.read = 0)"];
[request setPredicate:predicate];

NSError *err;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}

return count;
}

However I still got the following error. Am I still doing something wrong that I am missing?

2010-08-22 20:51:06.521 RSS Reader[6747:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath FeedItem.read not found in entity <NSSQLEntity FeedInfo id=1>'
*** Call stack at first throw:
(
0 CoreFoundation 0x027e0919 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x0292e5de objc_exception_throw + 47
2 CoreData 0x022cbb36 -[NSSQLGenerator generateSQLStatementForFetchRequest:ignoreInheritance:countOnly:] + 1254
3 CoreData 0x0231cdc0 -[NSSQLAdapter newCountStatementWithFetchRequest:] + 464
4 CoreData 0x0231cb5f -[NSSQLChannel selectCountWithFetchRequest:] + 79
5 CoreData 0x0231c6ef -[NSSQLCore countForFetchRequest:inContext:] + 399
6 CoreData 0x022ca510 -[NSSQLCore executeRequest:withContext:error:] + 368
7 CoreData 0x023786ec -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 1084
8 CoreData 0x0231bb48 -[NSManagedObjectContext(_NSInternalAdditions) _countWithNoChangesForRequest:error:] + 296
9 CoreData 0x0231b3ca -[NSManagedObjectContext countForFetchRequest:error:] + 106
10 RSS Reader 0x0000f8e6 -[FeedHandler countUnreadItems] + 319
11 RSS Reader 0x00002359 -[RSS_ReaderAppDelegate applicationDidEnterBackground:] + 215
12 UIKit 0x002f7233 -[UIApplication _handleApplicationSuspend:eventInfo:] + 593
13 UIKit 0x002ffee0 -[UIApplication handleEvent:withNewEvent:] + 4660
14 UIKit 0x002f8074 -[UIApplication sendEvent:] + 71
15 UIKit 0x002fcac4 _UIApplicationHandleEvent + 7495
16 GraphicsServices 0x02fe7afa PurpleEventCallback + 1578
17 CoreFoundation 0x027c1dc4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
18 CoreFoundation 0x02722737 __CFRunLoopDoSource1 + 215
19 CoreFoundation 0x0271f9c3 __CFRunLoopRun + 979
20 CoreFoundation 0x0271f280 CFRunLoopRunSpecific + 208
21 CoreFoundation 0x0271f1a1 CFRunLoopRunInMode + 97
22 GraphicsServices 0x02fe62c8 GSEventRunModal + 217
23 GraphicsServices 0x02fe638d GSEventRun + 115
24 UIKit 0x00300b58 UIApplicationMain + 1160
25 RSS Reader 0x000020c0 main + 102
26 RSS Reader 0x00002051 start + 53
)
terminate called after throwing an instance of 'NSException'
Program received signal: “SIGABRT”.

If I take out ' AND (FeedItem.read = 0)' it works.

ppilone
Aug 23, 2010, 02:36 PM
Since you're goal is to count the number of items that are unread and being shown, it's probably better to form your fetch request around FeedItem. Use FeedItem as your entity name and form your predicate like:



NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(self.read == NO) AND (self.feedinfo.show == YES)"];



This will only fetch feed items that have not been read and who's feedinfo is being shown.

Also - note the use of the conditional (==) operator in the predicate and not the assignment (=) operator.

seepel
Aug 24, 2010, 04:40 AM
So I have changed my code to the following as per the reply. I'm using FeedInfo and so used FeedItem.read

- (int)countUnreadItems {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesSubentities:YES];
[request setEntity:[NSEntityDescription entityForName:@"FeedInfo" inManagedObjectContext:managedObjectContext]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@" (show = 1) AND (FeedItem.read = 0)"];
[request setPredicate:predicate];

NSError *err;
NSUInteger count = [managedObjectContext countForFetchRequest:request error:&err];
if(count == NSNotFound) {
//Handle error
}

return count;
}

However I still got the following error. Am I still doing something wrong that I am missing?



If I take out ' AND (FeedItem.read = 0)' it works.

So I wasn't very careful when I read your mdel, you would want feeditems.read. The part before the dot should be the relationship name.