It is unclear to me how NSArray's isEqual method compares elements of the two arrays. Does it check if both arrays contain identical objects (eg. ==) ? or does it compare the contents of both arrays using isEqual on the objects?
I find Apple's docs for this method terse and unclear. I can't find the source for NSArray.m either.
No answer exists in the modern Cocoa documentation, but if you go all the way back to WebObjects 3.5's NSArray documentation, you find this gem:
- (BOOL)isEqual:(id)anObject
Returns YES if the receiver and anObject are equal; otherwise returns NO. A YES return value indicates that the receiver and anObject are both instances of classes that inherit from NSArray and that they both contain the same objects (as determined by the isEqualToArray: method).
The closest thing to an answer outside of the legacy docs is this discussion of object comparison in the Coding Guidelines for Cocoa, which seems to imply that isEqual: and isEqualToWhatever: should do the same thing, with the only difference being the level of type safety.
Still, I recommend filing a bug to ask for the docs to be clarified.
The documentation is clear:
Two arrays have equal contents if they each hold the same number of objects and objects at a given index in each array satisfy the isEqual: test.
That means that isEqual (and not ==) wil be used to test objects for equality.
All objects in Cocoa are compared with -isEqual: by default. The default version of -isEqual: on NSObject, however, does a pointer comparison. So, if the object hasn't properly implemented its -isEqual: and -hash methods, it's going to simply compare pointers.
MAYBE its kinda like this!? (typed here so there're likely typos)
if(![array1 isKindOfClass:[NSArray class]] || ![array2 isKindOfClass:[NSArray class]])
return NO;
if(array1 == array2)
return YES;
if(array1.count != array2.count)
return NO;
for(int i =0; i<array1.count;i++)
if(![array1[i] isEqual:array[i]])
return NO;
return YES;
Related
So I have a situation where im using a class as a kind of struct. now i want to override the isEqual: method so if this type of object is inside an array, i can use [objects indexOfObject:obj]
but, now say obj contains objects called duck, and chicken, i would like to go [objects indexOfObject:duck] and it will actually give me the index of obj
so i tried something like this:
- (BOOL)isEqual:(id)anObject {
if([anObject isKindOfClass:[Duck class]]){
return [anObject isEqual:_duck];
}
else if([anObject isKindOfClass:[Chicken class]]){
return [anObject isEqual:_chicken];
}
return [anObject isEqual:self];
}
which doesnt work and isnt even getting called... think that has to do with overriding hash, which i tried by just returning self.hash (effectively obj.hash) but that didnt work.
Do you think something like this is possible? or should i just use for loops to search through all my obj's to find which duck is contained in which obj and return the index of it (which i can do, just wanted to try be cool and neat at the same time)
It sounds using -isEqual: is a bad idea here.
You can't have a DuckAndChicken that compares equal to its Duck and its Chicken, (and vice versa) because in order to stay consistent, all ducks and chickens would then have to compare equal.
Example:
duck1 + chicken1 compares equal to duck1
chicken1 compares equal to duck1 + chicken1
duck2 + chicken1 compares equal to chicken1
duck2 compares equal to duck1
Universe explodes
The good news is that you don't have to use -indexObject: to retrieve from the array. NSArray has a handy -indexOfObjectPassingTest: method that should do what you want.
[ducksAndChickens indexOfObjectPassingTest:^BOOL(DuckAndChicken *obj, NSUInteger idx, BOOL *stop) {
if ([obj.duck isEqual:myDuck]) {
// woop woop...
}
}];
Yes, it sounds like a hash issue.
I would avoid trying to do this by overriding isEqual: because you're liable to break things that you don't think you'd break.
Instead add a custom method that you can call to determine your version of equivalence. Then have a helper method or a category on NSArray which adds my_indexOfObject: and does the iteration.
Its hard to tell what you really want to do from your example but could you perhaps have two dictionaries one where ducks are the key and one where chickens are the key and the object value is either the actual parent object or a NSNumber with the index in the array. This would make lookup much quicker though would take up more memory, and could make synchronisation between the three data structs.
I hardly ever see the second one used and I wonder why?
Neither would it break support for situations where an NSArray is expected (as it's a subclass).
Nor would it break encapsulation by revealing mutable internals.
Under the precondition that it's never a mutable ivar that's returned, (which should be common sense anyway)
I can right now only think of advantages of using the second.
It actually is mutable. And muting is safe here, so why prevent it?
No need to call [[[foo fooBar] mutableCopy] autorelease], which needlessly allocates additional memory and needlessly wastes time.
Here are the method variations:
- (NSArray *)fooBar {
NSMutableArray *fooArray = [NSMutableArray array];
//populate fooArray
return fooArray;
}
- (NSMutableArray *)fooBar {
NSMutableArray *fooArray = [NSMutableArray array];
//populate fooArray
return fooArray;
}
I'm asking as my project has a bunch of methods with the same pattern.
And in most of the times the returned array will be modified afterwards (merged, edited, etc).
So I think it should be totally fine to return NSMutableArrays, yet nobody seems to be doing it.
NSMutableArray, NSMutableSet, NSMutableDictionary… it's basically the same deal.
For an explanation of using mutable versus immutable, check out Apple's documentation on Object Mutability.
In general, it is best to return an immutable version, unless it is specifically your intent that the object returned always be an immutable object available for any client to change. You should create your interfaces based on the intent of the interface, not off the current implementation. It is possible that requirements will change and you will need to change the implementation of fooBar such that it does return an instance variable. By returning mutable arrays you ensure that you encapsulate not only your instance variables, but your current implementation.
So, you may have a valid place to return a mutable array (I don't know), but you see most code passing immutable arrays because it fully encapsulates their variables and their implementations.
I suppose the first variation was preferred because polymorphism was preferred.
In either case, both methods return an instance of NSMutableArray, the only difference being that the first one hides that fact from the caller. In other words, the first variation is not safer than the second. It's essentially using polymorphism to tell the caller that any type of NSArray might be returned. If you need that kind of flexibility in your code, it definitely has it's advantages. (e.g., if one day, for whatever reason, you need to return a custom NSArray subclass, your code won't break at that level).
However, you seem to prefer communicating intent to the caller - i.e. that you actually return mutable arrays - which is also OK. To make everyone happy (if there is such thing anyways...), I suggest renaming the 2nd method to:
- (NSMutableArray *)mutableFooBar {
NSMutableArray *fooArray = [NSMutableArray array];
//populate fooArray
return fooArray;
}
As a side note, I think that the following is a slightly more efficient way to convert an existing immutable array into a mutable one:
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:fooArray];
(correct me if I'm wrong on that assumption).
I hope this answers your question...
Having a method return a mutable instance like that looks suspicious.
As the caller you have to question the original method signature and wonder if it really is safe to mutate the returned value. After all the class may inadvertently be returning a pointer to internal state.
If profiling reveals that this copy is indeed expensive, I usually change the method signature to make it obvious that the mutability is intended. Perhaps with something like:
- (void)populateFooBars:(NSMutableArray *)array;
That way it is clear that the mutability of the result is intentional.
I have a NSSet containing many thousands of NSValue objects (wrapping CGPoints). I would like to very quickly find if a given CGPoint value exists in the NSSet. It seems to me that the member: method of an NSSet might do the job here, except that it checks for equality using isEqual:. NSValue objects use isEqualToValue:, and so when I execute the code:
[mySet member:valueToCheck];
it actually causes Xcode to crash.
1) Is there some way to use a custom equality check to make this work for NSValue objects?
2) Is this even the best approach (i.e. is member: quick enough in the first place)? The scenario is that I have a NSSet containing a large number of points representing pixels on the screen (iPad). Later on I need to bombard that set with many thousands of points per second to see if they exist in the set. My approach seems crude for achieving this. I thought about creating something like a huge 2-dimensional bit array, with each index representing a pixel on screen. Once I know the point I'm testing for, I can just jump straight to that point in the array and check for a 1 or 0... does this sound better or worse?
Thanks
Can you get this to a simple reproducible case? For example, I just tried:
NSValue *v = [NSValue valueWithCGPoint:CGPointMake(1, 1)];
NSSet *s = [NSSet setWithObject:v];
NSLog(#"%#", [s member:[NSValue valueWithCGPoint:CGPointMake(1, 1)]]);
But it works just fine.
edit
-isEqual: is not the problem:
NSValue *v1 = [NSValue valueWithPoint:NSMakePoint(1, 1)];
NSValue *v2 = [NSValue valueWithPoint:NSMakePoint(1, 1)];
NSLog(#"%d", [v1 isEqual:v2]); //logs "1"
-hash is not the problem:
NSLog(#"%d", ([v1 hash] == [v2 hash])); //logs "1"
They are different objects:
NSLog(#"%d", (v1 != v2)); //logs "1"
The problem is in your code. Try cleaning and rebuilding.
To answer no. 2:
I don't know how NSSet is implemented internally, but considering that you know you are storing points (with X and Y), I think you would be better by implementing your own partitioning algorithm. Personally I would choose my own implementation over NSSet if you say you have thousands of points.
Storing huge 2-dimensional arrays for each pixel, would probably be the fastest way, but it will kill you in terms of memory consumption. You need something fast, but also lightweight.
There are a lot of algorithms out there and you can find them by searching "spatial partitioning algorithms" on wikipedia or google. It also depends on your programming skills, and how much time you are willing to invest in this.
For example, a pretty simple one would be to implement a quad-tree, where you start by diving your screen(or area) in 4 equal parts. Then if and where is needed, you divide that specific cell also in 4 parts. And you do this until each cell contains a small enough number of points so that you can brute-force test all of them.
You can find a very good description on wiki: http://en.wikipedia.org/wiki/Quadtree
Hope this helps,
[mySet member:valueToCheck] should not be crashing. NSValue's isEqual: works fine when I try it here, and in fact probably calls isEqualToValue: when given another NSValue to compare to. Is valueToCheck really an NSValue, or is it a CGPoint?
There is no way to override the default hash and comparison methods for NSSet. But NSSet is toll-free bridged with CFSetRef, and you can easily specify custom hashing and comparison methods there:
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
callbacks.equal = customEqualFunction;
callbacks.hash = customHashFunction;
NSMutableSet *set = (NSMutableSet *)CFSetCreateMutable(NULL, 0, &callbacks);
The constraints on these functions are presumably the same as on NSObject's hash and isEqual: methods, anything that is equal must have the same hash. The C-style prototypes for customEqualFunction and customHashFunction are described here and here.
One solution would be to subclass NSSet and override member: to do your own comparison. Your own comparison could then simple call isEqualToValue:. Have a look at the subclassing notes in the NSSet documentation.
Another approach would be to add a category to NSValue that implements isEqual:. In this case I'd prefer subclassing because it's a more constrained solution.
It's not just a problem with -isEqual:, you may also have an issue with the -hash method. If you want to use an NSSet, you should probably create a custom class that wraps the CGPoint. -isEqual: is then trivial and -hash could be implemented by some method of combining the bits of both coordinates and then treating them as a NSUInteger.
You'll also want to implement the NSCopying protocol which is also trivial if your points are immutable (just retain and return self in -copyWithZone:).
I need to detect change in NSArray object - that is if some object was added/removed to/from NSArray or was just edited in-place. Are there some integrated NSArray hash functions for this task - or I need to write my own hashing function for NSArray ? Maybe someone has different solution ? Any ideas ?
All objects have a -hash method but not all objects have a good implementation.
NSArray's documentation doesn't define it's result, but testing reveals it returns the length of the array - not very useful:
NSLog(#"%lu", #[#"foo"].hash); // output: 1
NSLog(#"%lu", #[#"foo", #"bar"].hash); // output: 2
NSLog(#"%lu", #[#"hello", #"world"].hash); // output: 2
If performance isn't critical, and if the array contains <NSCoding> objects then you can simply serialise the array to NSData which has a good -hash implementation:
[NSArchiver archivedDataWithRootObject:#[#"foo"]].hash // 194519622
[NSArchiver archivedDataWithRootObject:#[#"foo", #"bar"]].hash // 123459814
[NSArchiver archivedDataWithRootObject:#[#"hello", #"world"]].hash // 215474591
For better performance there should be an answer somewhere explaining how to write your own -hash method. Basically call -hash on every object in the array (assuming the array contains objects that can be hashed reliably) and combine each together mixed in with some simple randomising math.
You could use an NSArrayController, which is Key-Value-Observing compliant. Unfortunately NSArray is only KVC compliant. This way you can easily monitor the array controller's arrangedObjects property. This should solve your problem.
Also, see this question: Key-Value-Observing a to-many relationship in Cocoa
I have a database model class that is a NSObject. I have a set of these objects in a NSMutableArray. I use indexOfObject: to find a match. Problem is the model object's memory address changes. So I am overriding the hash method to return the model's row ID. This however does not fix it. I also have to override the isEqual: method to compare the value of the hash method.
What does the isEqual: method use to determine equality by default?
I'm assuming it uses the memory address. After reading the isEqual: documentation I thought it used the value from the hash method. Obviously, that is not the case as my attempt to override that value did not solve my initial problem.
As you've correctly guessed, NSObject's default isEqual: behaviour is comparing the memory address of the object. Strangely, this is not presently documented in the NSObject Class Reference, but it is documented in the Introspection documentation, which states:
The default NSObject implementation of isEqual: simply checks for pointer equality.
Of course, as you are doubtless aware, subclasses of NSObject can override isEqual: to behave differently. For example, NSString's isEqual: method, when passed another NSString, will first check the address and then check for an exact literal match between the strings.
The answer about default implementation of isEqual: is comprehensive one. So I just add my note about default implementation of hash. Here it is:
-(unsigned)hash {return (unsigned)self;}
I.e it's just the same pointer value which is used in isEqual:. Here's how you can check this out:
NSObject *obj = [[NSObject alloc] init];
NSLog(#"obj: %#",obj);
NSLog(#"hash: %x",obj.hash);
The result will be something like this:
obj: <NSObject: 0x16d44010>
hash: 16d44010
Best Regards.
BTW in iOS 8 hash became a property not a method, but it's there.
I would assume that NSObject isEquals uses the == operator, and hash uses the memory address.
isEquals method should never uses hash as an absolute test for equality. It is guaranteed to have two objects having similar hashCode, if you search for enough objects (just create more than 2^32 different objects, and at least two of them will have the same hash).
In other words, hash requires the following spec: If two objects are equals, then their hash needs to be equal; however, if two objects' hash values are equals, they are not necessarily equal.
As a tip, you always should override isEquals and hashCode together.