I have a class called
Contact;
In Contact I have (simple version to test, no hash yet)
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return NO;
}
I do:
NSMutableArray *arr = [[NSMutableArray alloc] init];
Contact *contact = [Contact new];
[arr addObject:contact]
// I expect my isEqual to be called here but it does not get called
[arr containsObject:contact] // this evaluates to true somehow!!!
However if I add a another object to type NSString, then it gets called for comparing String object but not for the contact object. Which means
[arr addObject:#""] // so now arr has two elements
// here I expect two calls to isEqual but only one gets there
// when comparing string object against Contact
[arr containsObject:contact]
Why is isEqual not getting called in cases I mentioned above??
Please read the discussion about isEqual: in the NSObject Protocol in the Reference Library.
You'll find that for objects which are inside a collection (such as an NSArray), hash might be used to determine whether two objects are actually the same. If two pointers are actually pointing to the same object, there is no need to check for equality - hence isEqual: never gets called.
The solution as suggested by the reference library is to implement hash in your subclass as well.
Related
I have a simple question regarding xcode coding but don't know why things are not performing as I think. I have an array of objects (custom objects). I just want to check if this one is within the array. I used the following code:
NSArray *collection = [[NSArray alloc] initWithObjects:A, B, C, nil]; //A, B, C are custom "Item" objects
Item *tempItem = [[Item alloc] initWithLength:1 width:2 height:3]; //3 instance variables in "Item" objects
if([collection containsObject:tempItem]) {
NSLog(#"collection contains this item");
}
I suppose the above checking will give me a positive result but it's not. Further, I checked whether the objects created are the same.
NSLog(#"L:%i W:%i H:%i", itemToCheck.length, itemToCheck.width, itemToCheck.height);
for (int i = 0, i < [collection count], i++) {
Item *itemInArray = [collection objectAtIndex:i];
NSLog(#"collection contains L:%i W:%i H:%i", itemInArray.length, itemInArray.width, itemInArrayheight);
}
In the console, this is what I got:
L:1 W:2 H:3
collection contains L:0 W:0 H:0
collection contains L:1 W:2 H:3
collection contains L:6 W:8 H:2
Obviously the tempItem is inside the collection array but nothing shows up when I use containsObject: to check it. Could anyone give me some direction which part I am wrong? Thanks a lot!
The documentation for [NSArray containsObject:] says:
This method determines whether
anObject is present in the receiver by
sending an isEqual: message to each of
the receiver’s objects (and passing
anObject as the parameter to each
isEqual: message).
The problem is that you are comparing references to objects rather than the values of the objects. To make this specific example work, you will either need to send [collection containsObject:] an instance of a variable it contains (e.g. A, B, or C), or you will need to override the [NSObject isEqual:] method in your Item class.
This is what your isEqual method might look like:
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
if (self.length != other.length || self.width != other.width || self.height != other.height)
return NO;
return YES;
}
For a better implementation, you may want to look at this question.
I feel like there is a more regulation way to do what I am doing in, either by some iOS specific thing, or pattern I'm aware of. I'm trying to create an NSMutableArray variable, that essentially acts as temporary storage for a logger class. Each time the array is accessed, I want to either lazily instantiate it, or set it to nil. The way I am thinking of doing it seems a little hacky and I'm looking for some input?
- (NSMutableArray)myArray {
if (!_myArray) {
_myArray = [[NSMutableArray alloc] init];
} else {
_myArray = nil;
}
return _myArray;
}
The effect I'm hoping to achieve is using a logger that is logging details about network requests - http codes, URLs, repsonse times, etc. I want the logger to amalgamate all this output in this storage array. Later on, when I'm hitting an API, I want to take the contents of this array, and send it up to the API, and I also want the array to reset (so the array is essentially a log of network request data since the last time the app hits the API, versus a log of what has happened since the app launched.
I realise that I could do this manually by niling the array when I access it, but I'm trying to do this in a more of a plug and play way, where it you don't need to worry if someone forgets to nil the array etc
The effect that you are trying to achieve is perfectly legitimate, but you shouldn't try to achieve it with a getter alone: the very fact that a simple getter could reset something back to nil would be counter-intuitive to your readers.
Instead, you should make two methods - one to prepare the array, and another one to harvest it, and replace with a fresh nil:
- (NSMutableArray*)myArray {
if (!_myArray) {
_myArray = [[NSMutableArray alloc] init];
}
return _myArray;
}
- (NSMutableArray*)resetArray{
NSMutableArray *res = _myArray;
_myArray = nil;
return res;
}
Now the sequence of operations becomes intuitively clear: you get myArray as many times as you wish, add as many items as you need, and then when you are done call resetArray. This would get you a complete array with all the data, and reset the object to be ready for the next call:
for (int col = 0 ; col != 10 ; col++) {
[log.myArray addObject:[self getDataForIndex:col]];
}
NSMutableArray* logArray = [log resetArray];
What you're doing doesn't make any sense to me.
Creating it empty if it doesn't exist makes sense.
Setting it to nil if it does exist does not make sense.
The usual pattern for lazy loading is to use a property with a getter:
#property (nonatomic, retain) NSMutableArray * myArray;
and then the implementation:
//Custom getter
-(NSMutableArray*) myArray;
{
if (!_myArray)
_myArray = [[NSMutableArray alloc] init];
return _myArray;
}
Then you ALWAYS refer to the array using the getter. If it hasn't yet been created, the getter creates and returns the empty array. If it does exist, it returns the existing one.
I am trying to check if the NSMutableArray has a specific object, before adding the object to it, if exists then don't add.
i looked over many posts explaining how to do this, managed to implement it like this, but it always gives me that the object "doesn't exist", though i already added it !
//get row details into FieldLables Object
AllItemsFieldNames *FieldLabels = feedItems[row];
// object to hold single row detailes
AllItemsFieldNames *SelectedRowDetails = [[AllItemsFieldNames alloc] init];
SelectedRowDetails.item_name = FieldLabels.item_name;
//SelectedRowDetails.item_img = FieldLabels.item_img;
SelectedRowDetails.item_price = FieldLabels.item_price;
//NSLog(#"item has been added %#", SelectedRowDetails.item_name);
//NSLog(#"shopcartLength %lu", (unsigned long)SelectedFieldsNames.count);
if([SelectedFieldsNames containsObject:SelectedRowDetails])
{
NSLog(#"Already Exists!");
}
else
{
NSLog(#"Doesn't Exist!");
[SelectedFieldsNames addObject:SelectedRowDetails];
}
I can display all object from the NSMutableArray into a table, what i need to do in the above code is stop the addition of duplicate objects.
The first method listed on the NSArray documentation under the section "querying an array" is containsObject:. If it's not working, that suggests that your implementation of isEqual: is not correct. Make sure you follow the note in the documentation:
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
You might also consider using an NSSet since you can't add duplicates to that. Of course, this would also require a working version of isEqual:.
Sets are composed of unique elements, so this serves as a convenient way to remove all duplicates in an array.
here some sample,
NSMutableArray*array=[[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3",#"4", nil];
[array addObject:#"4"];
NSMutableSet*chk=[[NSMutableSet alloc ]initWithArray:array]; //finally initialize NSMutableArray to NSMutableSet
array= [[NSMutableArray alloc] initWithArray:[[chk allObjects] sortedArrayUsingSelector:#selector(compare:)]]; //after assign NSMutableSet to your NSMutableArray and sort your array,because sets are unordered.
NSLog(#"%#",array);//1,2,3,4
This question already has answers here:
How do I compare objects in Objective-C?
(4 answers)
Closed 8 years ago.
I have a list of Objects that I pull from a web service. When I update my UITableView, I retrieve the objects again from the web service, and compare them to each other for equality. I then remove the ones that are not present, and insert the new objects, then update my UITableView. How can I test to see if the new object equals the old object? I've created a test for clarity..
requestA should equal requestC, but fails.
Is this possible to do without looking at each property value as the objects have many values?
I was originally comparing the ID only, but this doesn't work as sometimes other property values change and the ID stays the same.
Request *requestA = [[Request alloc] init];
Request *requestB = [[Request alloc] init];
Request *requestC = [[Request alloc] init];
requestA.requestID = #"1";
requestA.productName = #"Clutch";
requestB.requestID = #"2";
requestB.productName = #"Wiper";
requestC.requestID = #"1";
requestC.productName = #"Clutch";
if (requestA == requestB)
NSLog(#"A == B");
if (requestA == requestC)
NSLog(#"A == C");
if ([requestA isEqual:requestB])
NSLog(#"A isEqual B");
if ([requestA isEqual:requestC])
NSLog(#"A isEqual C");
// Look at the pointers:
NSLog(#"%p", requestA);
NSLog(#"%p", requestB);
NSLog(#"%p", requestC);
isEqual: is a method declared in NSObject Protocol. From official docs of isEqual:
This method defines what it means for instances to be equal. For
example, a container object might define two containers as equal if
their corresponding objects all respond YES to an isEqual: request.
See the NSData, NSDictionary, NSArray, and NSString class
specifications for examples of the use of this method.
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
Thus, as Salavat Khanov pointed out in his answer:
You need to implement -isEqual: and -hash methods for your Request class.
You want to do something like this:
// TestRequest.h
#interface TestRequest : NSObject
#property (nonatomic) NSString *requestID;
#property (nonatomic) NSString *productName;
#end
// TestRequest.m
#import "TestRequest.h"
#implementation TestRequest
- (BOOL)isEqual:(TestRequest *)object {
if (self == object) {
return YES;
}
if (![self.requestID isEqual:object.requestID]) {
return NO;
}
if (![self.productName isEqual:object.productName]) {
return NO;
}
return YES;
}
- (NSUInteger)hash {
// this is a very simple hash function
return [self.requestID hash] ^ [self.productName hash];
}
#end
or you can use a custom method:
- (BOOL)isEqualToRequest:(TestRequest *)otherRequest {
return [self.requestID isEqualToString:otherRequest.requestID] &&
[self.productName isEqualToString:otherRequest.productName];
}
Check this answer: How do I compare objects in Objective-C?
You need to implement -isEqual: and -hash methods for your Request class.
You need to overwrite isEqual: of your Request object to specify the properties to compare.
Write sth. like this:
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
if (!other || ![other isKindOfClass:[self class]]) return NO;
if (![(id)[self name] isEqual:[other name]]) return NO;
// add other checks if needed
return YES;
}
First off. == is a check for "are these two objects actually the SAME OBJECT". I.e. They are just two pointers to the same but of memory.
You need to be using the isEqual method. However, in order to do this properly you need to override the method in the class.
Something like...
- (BOOL)isEqual:(Request *)otherObject
{
return [self.productName isEqual:otherObject.productName]
&& [self.requestID isEqual:otherObject.requestID];
}
I'm writing some code that will be using NSMutableArray and storing int values within it, wrapped within NSNumbers.
I would like to confirm that querying an iOS NSArray or NSMutableArray using new NSNumbers with same values is legal, of if I need to explicitly iterate over the array, and check if each int value is equal to the value I want to test against?
This appears to work:
NSMutableArray* walkableTiles = [NSMutableArray array];
[walkableTiles addObject:#(1)];
[walkableTiles addObject:#(2)];
[walkableTiles addObject:#(3)];
if([walkableTiles containsObject:#(1)])
{
DLog(#"contains 1"); //test passes
}
if([walkableTiles containsObject:[NSNumber numberWithFloat:2.0]])
{
DLog(#"contains 2");//test passes
}
if([walkableTiles containsObject:[NSNumber numberWithInt:3]])
{
DLog(#"contains 3");//test passes
}
What you are doing is fine. Why wouldn't it be?
The containsObject: method actually iterates over the array and calls the isEqual: method on each object passing in the object you are checking for.
BTW - there is nothing special here about using NSNumber. It's the same with an array of any object type. As long as the object's class has a valid isEqual: method, it will work.
Per the Apple's NSNumber documentation, you should use isEqualToNumber:
isEqualToNumber: Returns a Boolean value that indicates whether the
receiver and a given number are equal.
- (BOOL)isEqualToNumber:(NSNumber *)aNumber