Do
for (id object in array) {
// do something with object
}
guarantee to return the objects in the order they are put in the array?
It's just shorthand for an enumerator. So yes for NSArrays, no for NSSets and NSDictionarys
Related
So i'm overriding isEquals and hash to compare custom objects to be able to remove duplicates from a NSArray. The problem is that i'm missing some values in the list which contains no duplicated items, and it seems that my hash or isEquals implementation is wrong. The custom object is a Course object which has some variables like: id and name I'll put the code here:
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[Course self]]) {
return YES;
}
if(self == object){
return YES;
}
else {
return NO;
}
}
- (unsigned)hash {
NSString *idHash = [NSString stringWithFormat: #"%d", self._id];
return [idHash hash];
}
Then, after querying the database i put the values in an array and then in a set which should remove the duplicated items like this:
NSMutableSet *noDuplicates = [[NSMutableSet alloc] initWithArray:tempResults];
Can you see what i'm doing wrong in the isEquals or hash implementation?
Thanks a lot.
Step 1. Decide which instance variables / state are used to determine equality. It's a good idea to make sure properties exist for them (they can be private properties declared in a class extension if you like).
Step 2. Write a hash function based on those instance variables. If all the properties that count are objects, you can just xor their hashes together. You can also use C ints etc directly.
Step 3. Write isEqual: The normal pattern is probably to first test that both objects are in the class or a subclass of the method in which isEqual: is defined and then to test equality for all the properties.
So if a class Person has a name property (type NSString) and a number property (type int) which together define a unique person, hash might be:
-(NSUInteger) hash
{
return [[self name] hash] ^ [self number];
}
isEqual: might be
-(BOOL) isEqual: (id) rhs
{
BOOL ret = NO;
if ([rhs isKindOfClass: [Person class]]) // do not use [self class]
{
ret = [[self name] isEqualToString: [rhs name]] && [self number] == [rhs number];
}
return ret;
}
I don't think it is stated as an explicit requirement in the doc but it is probably assumed that equality is symmetric and transitive i.e.
[a isEqual: b] == [b isEqual: a] for all a and b
[a isEqual: b] && [b isEqual: c]implies [a isEqual: c] for all a, b, c
So you have to be careful if you override isEqual: for subclasses to make sure it works both ways round. This is also why the comment, do not use [self class] above.
Well, your isEqual: implementation really just tests if the two objects are the same class. That's not at all correct. Without knowing the details of your object, I don't know what a good implementation would look like, but it would probably follow the structure
- (BOOL)isEqual:(id)object {
if ([object isMemberOfClass:[self class]]) {
// test equality on all your important properties
// return YES if they all match
}
return NO;
}
Similarly, your hash is based on converting an int into a string and taking its hash. You could also just return the int itself as your hash.
Your code violates the principal of "objects that are equal should have equal hashes." Your hash method generates a hash from self._id and doesn't take that value into consideration when evaluating the equality of the objects.
Concepts in Objective-C Programming has a section on introspection where this topic has examples and coverage. isEqual is meant to answer the question of two objects are equivalent even if they are two distinct instances. So you want to return a BOOL indicating if the object should be considered equivalent. If you don't implement isEqual it will simply compare the pointer for equality which is not what you probably want.
- (BOOL)isEqual:(id)object {
BOOL result = NO;
if ([object isKindOfClass:[self class]]) {
result = [[self firstName] isEqualToString:[object firstName]] &&
[[self lastName] isEqualToString:[object lastName]] &&
[self age] == [object age];
}
return result;
}
From the NSObject Protocol Reference:
Returns an integer that can be used as a table address in a hash table
structure.
If two objects are equal (as determined by the isEqual: method), they
must have the same hash value. This last point is particularly
important if you define hash in a subclass and intend to put instances
of that subclass into a collection.
- (NSUInteger)hash {
NSUInteger result = 1;
NSUInteger prime = 31;
result = prime * result + [_firstName hash];
result = prime * result + [_lastName hash];
result = prime * result + _age;
return result;
}
So what defines two objects as equal is defined by the programmer and their needs. However, whatever methodology of equality is developed, equal objects should have equal hashes.
this is how you implement hash and isEqual (at-least the one which is working for me for purpose of identifying duplicates)
Hash Function
The Apple Doc says that the hash of two objects should be same for those which are considered equal( logically). hence I would implement the hash as below
-(unsigned int)hash
{
return 234;//some random constant
}
isEqual: method implemenation would be something like
-(BOOL)isEqual:(id)otherObject
{
MyClass *thisClassObj = (MyClass*)otherObject;
*// this could be replaced by any condition statement which proves logically that the two object are same even if they are two different instances*
return ([thisClassObj primaryKey] == [self primaryKey]);
}
More reference here : Techniques for implementing -hash on mutable Cocoa objects
Implementing -hash / -isEqual: / -isEqualTo...: for Objective-C collections
I have an array of dictionaries which contains Same keys but different values.
I have another dictionary and i want to check whether this dictionary is present in that array or not…???
suppose you have three keys in your sesond dictionary key1,key2,key3 so to get verification of presence of the dictionay in ayour array use 'NSPredicate` class like this
Suppose your array is _myDicArray and other dictionary is _refDic
NSPredicate* myPredicate =[NSPredicate predicateWithFormat:#"key1 like %# AND key2 like %# AND key3 like %#",[_refDic objectForKey:#"key1"],[_refDic objectForKey:#"key2"],[_refDic objectForKey:#"key3"]];
NSArray* someOtherArr = [[_myDicArray filteredArrayUsingPredicate:filmPredicate] objectAtIndex:0];
if([someOtherArr count] > 0)
//this is what you wanted ... this array has ur dic
I think this should also work
NSPredicate* myPredicate =[NSPredicate predicateWithFormat:#"self == %#",_refDic];
NSArray* someOtherArr = [[_myDicArray filteredArrayUsingPredicate:filmPredicate] objectAtIndex:0];
if([someOtherArr count] > 0)
//this is what you wanted ... this array has ur dic
Well, I would implement it this way.
Create a subclass of NSDictionary and implement these methods:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
It's very important that you properly implement hash. It should return different values for different dictionaries even if they are equal according to your definition. In isEqual you can check if both dictionaries contain the same keys and the same values. If yes, return YES, otherwise return NO.
With this your check is later merely one line: [arrayOfDictionaries containsObject:dictionaryIAmLookingFor];
If you implement improperly hash or skip implementing it, containsObject will not execute isEqual on all the objects in the array.
What is the diference between these two methods belonging to the NSSet class:
-(BOOL)containsObject:(id)anObject
-(id)member:(id)object
The answer lies in the return values. containsObject returns a YES or a NO depending on if the object you send belongs to that particular set.
member returns id, which means that it returns the actual object if that object is part of the set.
As an example, you have an NSSet, aSet, with anObject. anObject belongs to the set.
[aSet containsObject:anObject]; //returns YES
[aSet member:anObject]; //If the set contains an object equal to object (as determined by isEqual:) then that object (typically this will be object), otherwise nil.
If anObject does not exist in aSet:
[aSet containsObject:anObject]; //return NO
[aSet member:anObject]; //return nil
I have an array of current animal objects that can be viewed by day - for example, monday will return all animals that are available on a monday, etc.
I also have an array of saved animal objects.
How do I ensure that the saved animals don't show up in the current animals list?
Something like, if the currentAnimal.name isEqual to savedAnimal.name?
I need the objects in both arrays so it is important to compare the .name properties, I think?
Override isEqual and hash to do a comparison on the name if that is what you consider to make the objects 'equal'.
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [((MyObject *)other).name isEqualToString:name];
}
and
- (NSUInteger)hash {
return [name hash];
}
You should use isEqual method if you want objects strictly equals or method isKindOfClass.
Look at NSObject reference
What kind of information is stored behind such an enum type thing? Example:
typedef enum {
UIViewAnimationCurveEaseInOut,
UIViewAnimationCurveEaseIn,
UIViewAnimationCurveEaseOut,
UIViewAnimationCurveLinear
} UIViewAnimationCurve;
I am not sure if I can safely add such an enum constant to an array. Any idea?
Enums in Objective-C are exactly the same as those in C. Each item in your enum is automatically given an integer value, by default starting with zero.
For the example you provided: UIViewAnimationCurveEaseInOut would be 0; UIViewAnimationCurveEaseIn would be 1, and so on.
You can specify the value for the enum if required:
typedef enum {
UIViewAnimationCurveEaseInOut,
UIViewAnimationCurveEaseIn = 0,
UIViewAnimationCurveEaseOut,
UIViewAnimationCurveLinear
} UIViewAnimationCurve;
This result of this would be: UIViewAnimationCurveEaseInOut is 0; UIViewAnimationCurveEaseIn is 0; UIViewAnimationCurveEaseOut is 1; and so on. However, for basic purposes you shouldn't need to do anything like that; it just gives you some useful info to toy with.
It should be noted based on the above, that an enum can't assume to be a unique value; different enum identifiers can be equal in value to each other.
Adding an enum item to a NSArray is as simple as adding an integer. The only difference would be that you use the enum identifer instead.
[myArray addObject:[NSNumber numberWithInt:UIViewAnimationCurveEaseInOut]];
You can check this out for yourself by simply outputting each enum to the console and checking the value it provides you with. This gives you the opportunity to investigate the details of how it operates. But for the most part you won't really need to know on a day to day basis.
Enums are typically int values. You can store them in an array by wrapping them in an NSNumber:
[myMutableArray addObject:[NSNumber numberWithInt:myAnimationCurve]];
... then get them back out like this:
UIViewAnimationCurve myAnimationCurve = [[myMutableArray lastObject] intValue];
Enums in Objective-C are the same as enums in vanilla C. It's just an int. If you're using an NSArray, then it expects a pointer and you'll get a warning if you try to add an int to it:
NSMutableArray *myArray = [[NSMutableArray alloc] init];
[myArray addObject:UIViewAnimationCurveEaseInOut];
// Last line results in:
// warning: passing argument 1 of 'addObject:' makes
// pointer from integer without a cast
If you're storing a large collection of 32-bit integers, consider using the appropriate CF collection type rather than the NS collection type. These allow you to pass in custom retain methods, which gets rid of the need to box every integer added to the collection.
For example, let's say you want a straight array of 32-bit ints. Use:
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
The last parameter tells the array to not retain/release the "addresses" you pass in to it. So when you do something like this:
CFArrayAppendValue(arrayRef, 1);
What the array thinks is that you're passing in a pointer to an object living at the memory address 0x1. But since you told it to not call retain/release on that pointer, it gets treated as a standard int by the collection.
FWIW, for educational value, standard NSMutableArrays have equivalent CF types. Through toll-free bridging you can use the CF collection as a standard Foundation collection:
CFMutableArrayRef arrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, kCFTypeArrayCallbacks);
NSMutableArray *array = (NSMutableArray *)arrayRef;
[array addObject:#"hi there!"];
NSLog(#"%#", [array objectAtIndex:0]); // prints "hi there!"
You can apply the same tricks to dictionaries (with CFDictionary/CFMutableDictionary), sets (CFSet/CFMutableSet), etc.