CoreData - Getting a NSManagedObject's relationship subset - objective-c

Noob question incoming (very simple but not finding the answer anywhere):
I have an entity
MY_ENT *my_ent = {initialized elsewhere};
This entity has a to-many relationship called my_rel
NSLog(#"Relations: %lu", my_ent.my_rel.count);
Relations: 15
I'd like to get a subset of it, where the field my_field equals to #"xx"
I tried to loop on the relations populating a NSArray but no luck (pointers seem deallocated).
NSMutableArray *my_rels;
for (MY_REL *my_rel in my_ent.my_rel) {
if ([my_rel.my_field isEqualToString:#"xx"]) {
[my_rels addObject:my_rel];
}
}
Maybe I should use a predicate but I don't understand how to use one here.

Yes, it's easier with a predicate. You can do this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"my_field = %#", x];
NSSet *subset = [my_ent.my_rels filteredSetUsingPredicate:predicate];

Related

Objective-C Fast Enumeration: checking for BOOL

Scenario = I need to loop through an array and find how many "unread" there are and count how many to display to the user.
What I'm Looking For = something like this (this is not my real code)
for (NSDictionary *dic in self.objects) {
[unreadCountArray addObject:dic[#"wasRead"]];
for (YES in unreadCountArray) {
//statements
}
}
Question = Does anyone know how to loop through and find all of the YES booleans?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"wasRead = YES"];
NSArray *arr = [array filteredArrayUsingPredicate:predicate];
Can sort a thousand objects in 0.0004 seconds.
Then just do:
for (NSDictionary *object in arr) {
//Statements
}
Edit: actually after further experimentation, using fast-enumeration is about four times faster, about 0.0001, which if scaled to 100000 objects can be much, much faster.
NSMutableArray *test = [NSMutableArray array];
for (NSDictionary *dict in array)
if ([dict[#"theKey"] boolValue])
[test addObject:dict];
So for sorting, fast-enumeration is actually faster but for just a couple hundred objects, the performance increase is negligible.
And please before asking questions like this and getting downvotes, those could have been completely avoided by checking the documentation. Like this article and this article.
If you have an array of dictionaries, and you want to filter them, then filteredArrayUsingPredicate: is the method to use.
You can create a predicate using the key from your dictionary (predicateWithFormat:).
This will then give you an array of dictionaries that match the conditions in your predicate.
No sample code, I'm answering this on a phone.

NSPredicate to many like a <----->> b----->c

I have entities: Language, Proper and Answer.
model look like Language{A:name(NSString), R:propers(NSSet)} --->> Proper{A:name(NSString), R:answer(Answer)} ---> Answer{A:answer(NSString)}
So, i got NSDictionary with params: {#"key1", #"value1"}, {#"key2", #"value2"}... i
I need create NSPredicate from this dictionary to get all Languages where propers.name = key[i] and propers.answer.answer = value[i] from my NSDictionary.
Example:
C++
level : high
try/catch : yes
typization : static
Java
level : high
try/catch : yes
typization : dynamic
NSDictionary : {level : hight}, {try/catch : yes}, {typization : dynamic}
//make and set NSPredicate to array controller
//array controller arrangedObjects will return Java
Sorry for bad grammar :/
Update
*After 2 weeks of sleepless nights and work on expert system teacher took a laboratory without checking it. Kill me please. Thanks a lot to all of you.*
I am just guessing what you want to do so here is a code:
- (NSPredicate *)constructPredicateWithDictionary:(NSDictionary *)dictionary
{
NSArray *allKeys = [dictionary allKeys];
NSMutableArray *predicates = [NSMutableArray array];
for (NSString *key in allKeys) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(B, $B, $B.key = %# && $B.C.value = %#).#count > 0", key, [dictionary valueForKey:key]];
[predicates addObject:predicate];
}
//not quite sure what you need so I am guessing
NSPredicate *finalAndPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; //if you want all the predicates to be concatenated with and '&&' - logical expression - so all of the subqueries have to be correct
NSPredicate *finalOrPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates]; //if you want all the predicates to be concatenated with or '||' - logical expression - so any of the subqueries has to be correct
return finalOrPredicate; //return the needed predicate
}
You need from SUBQUERY. Something like:
[NSPredicate predicateWithFormat:#"B.key = %# AND SUBQUERY(B, $B, $B.C.value = %#).#count > 0", key, value];
The SUBQUERY iterates through the objects and you can also have nested SUBQUERYs if you have more than one to-many relationships.
You can use and "ANY ..." but it doesn't work in all cases.

Magical Record, CoreData, deleting a record and renumbering

I am adding functionality to delete a song (playlistTrack) from a Playlist. Each playlistTrack has a playlist_track_number associated with it, so we know what order the songs are to be played in. Thus, after deleting a playlistTrack I need to assign all the playlistTracks that follow a new playlist_track_number, namely 1 less than their previous playlist_track_number.
My code here runs fine once and then crashes with the console displaying "'Can't do regex matching on object 3." any help?
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSPredicate *predicateList = [NSPredicate predicateWithFormat:#"(playlist.name CONTAINS[cd] %#)", activePlaylistString];
NSArray *newArray = [[NSArray alloc]init];
newArray = (NSArray*)[Playlist_Track MR_findAllWithPredicate:predicateList];
for (int i = (delTrack + 1); i <= [newArray count]; i++) {
localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSString *nextTrackNumberString = [NSString stringWithFormat:#"%i",i];
NSPredicate *nextPredicate = [NSPredicate predicateWithFormat:#"(playlist.name CONTAINS[cd] %#) AND (playlist_track_number MATCHES [cd] %#)", activePlaylistString, nextTrackNumberString];
nextTrack = [Playlist_Track MR_findFirstWithPredicate:nextPredicate inContext:localContext];
if (nextTrack) {
int j = i-1;
nextTrack.playlist_track_numberValue = j;
[localContext MR_saveToPersistentStoreWithCompletion:nil];
}
if (!nextTrack) {
//Do Nothing
}
}
Instead of doing lots of different fetches, just do one fetch to get all of the objects that need to be updated, then iterate over them and perform the updates. Depending on the number if items you can batch the response from the fetch or return the items as faults and then batch the saves.
This error tell about you trying to use 'NSPredicate' with inappropriate types
(playlist_track_number MATCHES [cd] %#)
I suppose you are trying to MATCH number playlist_track_number with string.
BTW i suppose have a not very efficient architecture in your CoreData.
This delete operation will took a very long time
(find and fetch all than multiple find one in a loop)
Also your current architecture does not allow to use one playlistTrack in 2 or more playlists
So it is good idea to remove playlist_track_number property from playlistTrack
To create fast inserts and removes from playlist you can use linked list or doubly-linked list structure. When each playlistTrack item will have relationship to next (and previous) item.
In playlist entity you can have only relationship to head (and tail) items also good to have count property.
Also you can use NSOrderedSet for one-to-many relationship in CoreData
this will allow you to insert item at index and remove item at index
(But it have a lot of unresolved bugs for current moment)
Both variants will allow you to have track number/order without playlist_track_number property
Using Flop's suggestion, I reworked the code as follows. This is hardcoded to always delete the track at index 3, but that can be changed easily by passing a parameter into the method.
NSArray *trackList = [[NSArray alloc]init];
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
Playlist *selectedPlaylist;
selectedPlaylist = [Playlist MR_findFirstByAttribute:#"name" withValue:activePlaylistString];
trackList = (NSArray*)selectedPlaylist.playlist_trackSet;
NSMutableArray *newArray = [trackList mutableCopy];
NSUInteger delIndex = 3;
[newArray removeObjectAtIndex:delIndex];
trackList = newArray;
NSOrderedSet *trackSet = (NSOrderedSet*)trackList;
selectedPlaylist.playlist_track = trackSet;
[localContext MR_saveToPersistentStoreWithCompletion:nil];

NSPredicate to filter out all items that are in another set

Is there any way to do this? I have a set of items that I want to exclude from another set. I know I could loop through each item in my set and only add it to my filteredSet if it's not in the other set, but it would be nice if I could use a predicate.
The set of items to exclude isn't a set of the same type of object directly; it's a set of strings; and I want to exclude anything from my first set if one of the attributes matches that string.... in other words:
NSMutableArray *filteredArray = [NSMutableArray arrayWithCapacity:self.questionChoices.count];
BOOL found;
for (QuestionChoice *questionChoice in self.questionChoices)
{
found = NO;
for (Answer *answer in self.answers)
{
if ([answer.units isEqualToString:questionChoice.code])
{
found = YES;
break;
}
}
if (!found)
[filteredArray addObject:questionChoice];
}
Can this be done with a predicate instead?
This predicate format string should work:
#"NONE %#.units == code", self.answers
Combine it with the appropriate NSArray filtering method. If self.questions is a regular immutable NSArray, it would look something like
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NONE %#.units == code", self.answers]
NSArray *results = [self.questions filteredArrayUsingPredicate:predicate];
If it's an NSMutableArray, the appropriate usage would be
[self.questions filterUsingPredicate:predicate];
Be careful with that last one though, it modifies the existing array to fit the result. You can create a copy of the array and filter the copy to avoid that, if you need to.
Reference:
NSArray Class Reference
NSMutableArray Class Reference
Predicate Programming Guide
Check out the example given by Apple for using predicates with arrays. It employs filteredArrayUsingPredicate.

Getting unique items from NSMutableArray

I have a question about Objective-C today involving NSMutableArray. Coming from a .net/c# background I'm having some trouble working with these things.
Let's say I have an object called "Song"
My song has 3 properties:
Title
Artist
Genre
I have a NSMutableArray or NSArray which holds all my Song objects.
How would I go about trying to 'query' my array to get a new array with only (Unique) Artists or Genre's.
Where as in .net you would write a simple LINQ query with a DISTINCT clause, how would one solve this in Objective-C? I'm guessing with predicates but am struggling to find a solution.
You could also use:
NSArray *uniqueArtists = [songs valueForKeyPath:#"#distinctUnionOfObjects.artist"];
NSArray *uniqueGenres = [songs valueForKeyPath:#"#distinctUnionOfObjects.genre"];
Likewise, if you need to compare the entire object you could create a new readonly property that combines the values you want to match on (via a hash or otherwise) dynamically and compare on that:
NSArray *array = [songs valueForKeyPath:#"#distinctUnionOfObjects.hash"];
NOTE: Keep in mind this returns the uniques values for the specified property, not the objects themselves. So it will be an array of NSString values, not Song values.
Depending on what you mean by "unique artists," there are a couple of different solutions. If you just want to get all the artists and don't want any to appear more than once, just do [NSSet setWithArray:[songArray valueForKey:#"artist"]]. If you mean you want to set a list of all the songs which are the only song by their artist, you need to first find the artists who only do one song and then find the songs by those artists:
NSCountedSet *artistsWithCounts = [NSCountedSet setWithArray:[songArray valueForKey:#"artist"]];
NSMutableSet *uniqueArtists = [NSMutableSet set];
for (id artist in artistsWithCounts)
if ([artistsWithCounts countForObject:artist] == 1])
[uniqueArtists addObject:artist];
NSPredicate *findUniqueArtists = [NSPredicate predicateWithFormat:#"artist IN %#", uniqueArtists];
NSArray *songsWithUniqueArtists = [songArray filteredArrayUsingPredicate:findUniqueArtists];
Or like this:
filterTags = [[NSSet setWithArray:filterTags] allObjects];
filterTags was not unique at first, but becomes unique after the operation.
This may not be directly applicable but it is a solution to creating a unique set of values...
Assume you have a NSMutable array of events that have multiple duplicate entries. This array is named eventArray. The NSSet object can be used to trim that array and then repopulate that array as shown below...
NSSet *uniqueEvents = [NSSet setWithArray:eventArray];
[eventArray removeAllObjects];
[eventArray addObjectsFromArray:[uniqueEvents allObjects]];
Use NSOrderedSet instead of NSSet.
NSOrderedSet *uniqueOrderedSet = [NSOrderedSet setWithArray:itemsArray];
[itemsArray removeAllObjects];
[itemsArray addObjectsFromArray:[uniqueOrderedSet allObjects]];
I've created a simple query API for Objective-C that makes this kind of task a lot easier. Using the Linq-to-ObjectiveC distinct method, retrieving songs with unique artists would involve the following:
NSArray* songsWithUniqueArtistists = [input distinct:^id(id song) {
return [song artist];
}];
This returns a list of song instances, each with a unique artist.