Can I use the [NSArray containsObject:] function on attributes of objects? - xcode

Background Information:
I program an iOS application that contains 2 galleries: A local gallery and a server gallery. When the user updates the server gallery and merges it into his local one, the app should only download the new images.
To minimize memory consumption I save the images and fill the arrays with instances of an ImageEntity class with following attributes: fileName, filePath & votingStatus.
I tried to use following logic to check if the image already exists:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if (![self.localImagesArray containsObject:imageEntity]){
[self.localImagesArray addObject:imageEntity];
}
}
But because each entity is a separate object it will always be added. Each entity has a unique fileName though.
Question:
Can I somehow extend the [NSArray containsObject:] function to check if one object in the array has an attribute equal to "someValue"? (When I use Cocoa-Bindings in combination with an ArrayController, I can assign attributes of array elements - I would like to access the attributes similar to this).
I know that I could use compare each entity of the local array to each element on the server array. I would have to do O(n^2) comparisons though and the gallery may contain several hundred images.
Bonus question: Am I already doing this without realizing it? Does anybody have details about Apple's implementation of this function? Is there some fancy implementation or are they just iterating over the array comparing every element?

The way I do it is use valueForKey: in combination with containsObject:. So in your case you should collect all file names of the array and then check if the array contains specific file name you need:
NSArray * fileNames = [fileEntityObjects valueForKey:#"fileName"];
BOOL contains = [fileNames containsObject:#"someFilename.jpg"];
This would work if fileName is property of every object in fileEntityObjects array.
Update
Yes, you can do this with NSPredicate as well:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SELF.fileName = %#", "someFileName.jpg"];
NSArray * filteredArray = [fileEntityObjects filteredArrayUsingPredicate:predicate];
Note though that instead of boolean you'd get an array of objects having that filename.

As this question is tagged "performance" I'm adding another answer:
To avoid the n^2 comparisons we have to find a faster way to detect images that are already present. To do this we use a set that can perform lookups very quickly:
NSMutableSet *localFileNames = [NSMutableSet set];
for (ImageEntity *imageEntity in self.localImagesArray)
[localFileNames addObject:imageEntity.fileName];
Then we iterate over the server images as before. The previous containsObject: is replaced by the fast set lookup:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if ([localFileNames member:imageEntity.fileName] == nil)
[self.localImagesArray addObject:imageEntity];
}

Related

NSMutableArray to NSDictionary and then add to an NSArray

My app stores images in an NSMutableArray. I then call those objects and then send them through email in the mailSender.parts section of the code below. The problem is it only adds the first objectatindex when I need to add all objects. I am confused on how to make each image in the self.arrSlidshowImg NSMutableArray add to the vcfPart2 NSDictionary and then add it as an array object so the mailSender.parts will send all images. Any thoughts? I should also note that I did an NSLog to see the results adding this code NSLog(#"VCF: %#", vcfPart2);. The log file showed each value in vcfPart2. So the code is calling each response.
NSDictionary *vcfPart2;
for (int i = 0; i < self.arrSlidshowImg.count; i++) {
NSData *vcfData = [self.arrSlidshowImg objectAtIndex:i];
vcfPart2 = [JFMailSender partWithType:PartTypeFilePart
Message:[vcfData base64EncodedStringWithOptions:0]
ContentType:#"image/jpeg"
ContentTransferEncoding:#"base64"
FileName:[NSString stringWithFormat:#"Individual_%d", i]];
}
mailSender.parts = [NSArray arrayWithObjects:plainPart2,vcfPart2,nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[mailSender sendMail];
});
});
The following comes from a simple reading of your code, and among other things I have not looked up JFMailSender:
You declare a variable vcfPart2 to hold a reference to a dictionary
You enter a loop
In the loop you assign a value to vcfPart2, this value is presumably a reference to a dictionary as you report no warnings
Step 3 is executed self.arrSlidshowImg.count times, ice per iteration of the loop
You exit the loop, at this point vcfPart2 will hold the last reference assigned to it, the previous self.arrSlidshowImg.count - 1 having been overwritten
Your concern appears to be that when you then use vcfPart2 it only references one dictionary - but that is all it can ever do, that is it's type.
Maybe you intended to create a dictionary in your loop and then add that dictionary to a mutable array so that after the loop the array contains all the dictionaries?
HTH

Why does [NSSet containsObject] fail for SKNode members in iOS8?

Two objects are added to an NSSet, but when I check membership, I can't find one of them.
The test code below worked fine in iOS7 but fails in iOS8.
SKNode *changingNode = [SKNode node];
SKNode *unchangingNode = [SKNode node];
NSSet *nodes = [NSSet setWithObjects:unchangingNode, changingNode, nil];
changingNode.position = CGPointMake(1.0f, 1.0f);
if ([nodes containsObject:changingNode]) {
printf("found node\n");
} else {
printf("could not find node\n");
}
Output:
could not find node
What happened between iOS7 and iOS8, and how can I fix it?
SKNode's implementations of isEqual and hash have changed in iOS8 to include data members of the object (and not just the memory address of the object).
The Apple documentation for collections warns about this exact situation:
If mutable objects are stored in a set, either the hash method of the
objects shouldn’t depend on the internal state of the mutable objects
or the mutable objects shouldn’t be modified while they’re in the set.
For example, a mutable dictionary can be put in a set, but you must
not change it while it is in there.
And, more directly, here:
Storing mutable objects in collection objects can cause problems.
Certain collections can become invalid or even corrupt if objects they
contain mutate because, by mutating, these objects can affect the way
they are placed in the collection.
The general situation is described in other questions in detail. However, I'll repeat the explanation for the SKNode example, hoping it helps those who discovered this problem with the upgrade to iOS8.
In the example, the SKNode object changingNode is inserted into the NSSet (implemented using a hash table). The hash value of the object is computed, and it is assigned a bucket in the hash table: let's say bucket 1.
SKNode *changingNode = [SKNode node];
SKNode *unchangingNode = [SKNode node];
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
NSSet *nodes = [NSSet setWithObjects:unchangingNode, changingNode, nil];
Output:
pointer 790756a0 hash 838599421
Then changingNode is modified. The modification results in a change to the object's hash value. (In iOS7, changing the object like this did not change its hash value.)
changingNode.position = CGPointMake(1.0f, 1.0f);
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
Output:
pointer 790756a0 hash 3025143289
Now when containsObject is called, the computed hash value is (likely) assigned to a different bucket: say bucket 2. All objects in bucket 2 are compared to the test object using isEqual, but of course all return NO.
In a real-life example, the modification to changedObject probably happens elsewhere. If you try to debug at the location of the containsObject call, you might be confused to find that the collection contains an object with the exact same address and hash value as the lookup object, and yet the lookup fails.
Alternate Implementations (each with their own set of problems)
Only use unchanging objects in collections.
Only put objects in collections when you have complete control, now
and forever, over their implementations of isEqual and hash.
Track a set of (non-retained) pointers rather than a set of objects: [NSSet setWithObject:[NSValue valueWithPointer:(void *)changingNode]]
Use a different collection. For instance, NSArray will be affected by changes to
isEqual but won't be affected by changes to hash. (Of course, if
you try to keep the array sorted for quicker lookup, you'll have
similar problems.)
Often this is the best alternative for my real-world situations: Use an NSDictionary where the key is the [NSValue valueWithPointer] and the object is the retained pointer. This gives me: quick lookup of an object that will be valid even if the object changes; quick deletion; and retention of objects put in the collection.
Similar to the last, with different semantics and some other useful options: Use an NSMapTable with option NSMapTableObjectPointerPersonality so that key objects are treated as pointers for hashing and equality.

Objective-C EXC_BAD_ACCESS

Ok so I've recently decided to try to teach myself Objective-C (I'm interested in iPhone development), however I've never used C or any of its derivatives before, and as such am running into some problems.
I decided to start out by writing a very basic card application that creates a deck of cards, shuffles the deck, and then displays the cards on the screen using UIButtons, however I'm having a problem with my shuffling algorithm. Every time it gets called I get an EXC_BAD_ACCESS error, which I know means there's something desperately wrong with my code, but I just can't figure out what it is.
- (void) randomize {
NSMutableArray *tmpDeck = [[NSMutableArray alloc] init];
for(Card *tmp in _cards) {
BOOL didInsert = NO;
while(!didInsert) {
NSUInteger random = arc4random_uniform(54);
if([[tmpDeck objectAtIndex:random] isEqual:nil]) {
[tmpDeck insertObject:tmp atIndex:random];
didInsert = YES;
}
}
}
_cards = tmpDeck;
_hasBeenRandomized = YES;
}
_cards is a pointer to an NSMutableArray containing the unshuffled deck of card objects, and _hasBeenRandomized is a boolean (obviously) that keeps track of whether or not the deck has been randomized.
I've tried to use the debugger to work out what exactly is going on here, but I can't even step into the method without my program crashing. This leads me to believe that the problem has to come from the very first line, but it's just a straightforward creation of an NSMutableArray, so I don't know how it could be that. This method is being called from within viewDidLoad. This is the entirety of the viewDidLoad method currently.
- (void)viewDidLoad
{
[super viewDidLoad];
_deck = [[Deck alloc] init];
[_deck randomize];
}
Any and all help will be appreciated. Sorry if the answer is dead obvious.
This is because you are trying to insert into an index that doesn't exist yet. You need to initialize the array with as many places in the array as you need for your cards. Either that or use a NSMutableDictionary and just insert the object with the index being the key.
To add another note, calling initWithCapacity on the array wouldn't solve this for you either since this just gives a "hint" at the size. You need the count property of the array to actually be at least as large as the index you are trying to insert. If you wanted to do an array, then you would first need to populate something in each index first. You could define this in the new array literal format or use a for loop that loops the number of times you need (your max index) and insert a dummy object in it's place.
for (int i=0; i< _cards.count; ++i)
{
[tmpDeck insertObject:#"dummy" atIndex:i];
}
Then instead of checking for 'nil' before you replace, you check if it is equal to the dummy object you inserted. This would give you an array that you can insert into any of these indexes. I personally would still probably store them in an NSMutableDictionary. But if you need it in an array for some other purpose then this is a way to do it.
You also will need to be sure to replace the object instead of inserting, otherwise you will just keep adding indexes.
[tmpDeck replaceObjectAtIndex:random withObject:tmp];
If you still get the same error, set a breakpoint in your debugger and check what the random number is and what the count of your array is. If your random number is ever greater than your array count, then you will get this error.

NSDictionary sets values for NSMutableDictionary

I'm trying to make an rpg style game. I would like to know if and how to make an NSDictionary set the value for a mutable dictionary. The characters in my game will learn moves at different levels and the user can decide which move to use. If more info is needed let me now.
Here's an example
the NSDictionary pulls from a plist of attacks.
the attacks have names and power values.
the mutable array will set its attacks to the attacks in the NSDictionary.
You're looking for NSDictionary's - (NSArray *)objectsForKeys:(NSArray *)keys notFoundMarker:(id)anObject
Which
Returns the set of objects from the dictionary that corresponds to the specified keys as an NSArray.
Parameters keys An NSArray containing the keys for which to return corresponding values.
anObject The marker object to place in the corresponding element of the returned
array if an object isn’t found in the dictionary to correspond to a given key.
Discussion The objects in the returned array and the keys array have a one-for-one correspondence, so that the nth object in the returned array corresponds to the nth key in keys.
For example, you could run [dict objectsForKeys:[NSArray arrayWithObjects:str1, str2, nil] notFountMarker:[NSNull null]]; which will return the objects for the keys stored in str1, and str2. If the keys aren't found, the place holder will be an NSNull object.
If you're just after a mutable copy of a dictionary . . .
NSDictionary *atacks = /* Your dictionary of attacks */
NSMutableDictionary *mutable = [NSMutableDictionary dictionaryWithDictionary:attacks];

How do I get Core Data to use my own NSManagedObjectID URI scheme?

I am writing an app that connects to a database to fetch data. Since the fetching is expensive and the data is generally unchanging, I'm using CoreData to cache the results so that I can do fast, local queries.
From the database, for each type, there is a string property that is guaranteed to be unique. In fact, there is a URI scheme for the database which is a unique address for each item.
The URL scheme is very basic along the lines of:
ngaobject://<server_license_id>/<type>/<identifier>
I'd like to be able to use this in CoreData as well. I've made a method to fetch a single item from the CoreData store:
-(NSFetchRequest*)fetchRequestForType:(NSString*)typeName identifier:(NSString*)identifier
{
NSFetchRequest * fetchRequest = [self fetchRequestForType:typeName];
[fetchRequest setFetchLimit:1];
NSString * identifierProperty = [self identifierPropertyNameForObjectType:typeName];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"%K == %#", identifierProperty, identifier];
[fetchRequest setPredicate:predicate];
return fetchRequest;
}
-(NGAObject*)objectWithType:(NSString*)typeName
identifier:(NSString*)identifier
{
// First try to retrieve it from the cache
NSAssert1( (identifier != nil), #"Request to create nil-name object of type %#", typeName );
NSFetchRequest * fetchRequest = [self fetchRequestForType:typeName identifier:identifier];
if ( !fetchRequest )
return nil;
NSError * error = nil;
NSArray * fetchResults = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
if ( !fetchResults )
{
NSLog(#"%#", error);
[NSApp presentError:error];
return nil;
}
if ( [fetchResults count] )
return [fetchResults objectAtIndex:0];
return nil;
}
When I retrieve an item from the server, I want to first get a reference to it in the cache and if it's there, update it. If it's not, create a new one.
Since I'm getting back thousands of objects from the server, performing a fetch for a single object for which I know a unique ID brings my machine to a crawl.
Instead, what I'm doing is pre-loading all the objects of a type, then creating a dictionary of identifiers->object, then processing the thousands of objects for that type by running it through the dictionary. This works fine, but is awkward.
Could I not write a method that takes the type/identifier combo and get a single object from CoreData without having to execute a lengthy fetch request?
It seems there is a solution if I can get CoreData to use my own URI specification. I could then call -(NSManagedObjectID*)managedObjectIDForURIRepresentation:(NSURL*)url on the persistent store coordinator.
So, the question is, how can I get CoreData to use my URI scheme? How can I make CoreData use my own unique identifiers?
You can't make Core Data use a custom URI scheme. The URI scheme is hardcoded into Core Data such that the URI can be decoded to locate particular data in a particular store in a particular apps on a particular piece of hardware. If the URI was customizable, that system would break down.
Fetching object singularly is what is killing you. Instead you need to batch fetch all objects whose customID matches those provided by the server. The easiest way to that is to use the IN predicate operator:
NSArray *customIDs=//... array of customIDs provided by server
NSPredicate *p;
p=[NSPredicate predicateWithFormat: #"customIdAtrribute IN %#", customIDs];
This will return all existing objects that you can ignore.
Alternatively, you could
Do a fetch on just the customID property by setting the fetch's propertiesToFetch to the customID attribute.
Set the fetch result type to dictionary.
Use the above predicate.
You will get an array of one key dictionaries returned with the customID as each value.
Convert the dictionary to an array of values e.g cachedIDs
Convert customIDs above to a mutable array.
Filter the customIDs array using the predicate, #"NOT (SELF IN %#)", cachedIDs"
The filtered customIDs array will now only contain the customID values NOT cached in Core Data.
You can create managed objects for only the new ids.
(This is how you use a filter predicate if you are unfamilar with it.)
NSMutableArray *f=[NSMutableArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",nil];
NSArray *g=[NSArray arrayWithObjects:#"5",#"6",nil];
[f filterUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (SELF IN %#)",g]];
NSLog(#"f=%#",f);
...which outputs:
f=(
1,
2,
3,
4
)
Are all the fields which you are using for unique-ID lookup marked as "Indexed" in the CoreData designer? If that has been done then the CoreData fetches shouldn't be lengthy ...

Resources