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];
}
Related
We know NSArray is immutable and NSMutableArray is mutable,because we can judge by it's name.But how can we know a custom class whether mutable?
I believe there's no special interface or API in Objective-C or OS library to do this.
You can start by checking the known types like so:
if ([obj isKindOfClass:[NSMutableArray class]]) {
return YES; // mutable
}
if ([obj isKindOfClass:[NSMutableSet class]]) {
return YES; // mutable
}
... etc.
And for your own custom objects you can create your own custom "marker" protocol:
#protocol MYMutable <NSObject>
#end
(you apply that protocol to your own objects)
And then check your objects like so:
if ([obj conformsToProtocol:#protocol(MYMutable)]) {
return YES;
}
This is not perfect solution, but it might cover 90% of the use cases.
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.
I have a table view with a search, and search scope buttons with two possible scopes. The table is empty until a search is executed. Each scope has it's own mutable array for the table's data source, we'll say scopeA_array and scopeB_array. To simplify some methods, I'm trying to create a generic pointer reference to whichever array is the currently active scope. So I tried this:
#property (nonatomic, assign) NSMutableArray *tableDataArray;
In viewDidLoad, I assign it to the default selected scope.
_tableDataArray = _scopeA_array;
I can log the memory address of each array, they're both the same.
However, if I execute a search, _scopeA_array gets populated. Then in my numberOfRowsInSection method, I take the count of _tableDataArray but it's empty. I log the addresses again, both are different.
How do I create an array property that just references an array, and always points to the same object in memory even if it changes?
EDIT: A simplified way to test this, with the following lines of code, would like a way for tableDataArray to have the contents of testArray, even though the contents of testArray are assigned after:
NSArray *testArray = [NSArray new];
NSArray *tableDataArray = [testArray copy];
testArray = [NSArray arrayWithObjects:#"my", #"test", #"array", nil];
NSLog(#"table data array: %#", tableDataArray);
// logs empty array
I think the best approach is use a method to return conditionally the array for the current scope. So you just always use this method to populate your UITableView
- (NSMutableArray*) tableArray
{
return [self isScopeA] ? _scopeA_array : _scopeB_array;
}
How do I create an array property that just references an array, and always points to the same object in memory even if it changes?
If you want to track changes to a variable then you use a pointer to the variable rather than a pointer to a single array instance. E.g.:
#implementation MyController
{
__strong NSArray* *_currentDataPtr;
NSArray* _dataA;
NSArray* _dataB;
}
- (id)init
{
if (self = [super init])
{
_currentDataPtr = &_dataA; // Ensure _currentDataPtr is never NULL
}
return self;
}
- (void)setSearchScope:(NSInteger)searchScope
{
switch (searchScope)
{
default :
NSAssert(NO, #"");
case 0 :
_currentDataPtr = &_dataA;
break;
case 1 :
_currentDataPtr = &_dataB;
break;
}
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [*_currentDataPtr count];
}
If you want it to be a property then implement a property getter that dereferences the pointer:
#property (nonatomic, readonly) NSArray* currentData;
- (NSArray*)currentData { return *_currentDataPtr; }
Can I create category that will extend - (id)valueForKey:(NSString *)key method and if the value of this key will be equal [NSNull null] it will return me empty string #"".
It is connected to nil values for some keys that I get from the backend server in JSON.
So if I need to save some value to the property it can be nil.
Suppose I use this code with value for key nil
artist.name = ad[#"artist"][#"name"]; // nil value on the right.
so if I will have extension it will check value for key and return me #"" intend of nil. Does it make sense.
Or do I need extend NSManagedObject for checking attribute type and then return corresponded value #"", 0 or nil, or 0.0f if the attribute has another not string type.
Rather than defining a category on NSDictionary, you could define a category on NSObject, and do the conversion there:
#interface NSObject (DBNullHandling)
-(NSString*)nullToEmpty;
#end
#implementation NSObject (DBNullHandling)
-(id)nullToEmpty {
return (self == [NSNull null]) ? #"" : self;
}
#end
Now you can do this:
artist.name = [ad[#"artist"][#"name"] nullToEmpty];
If ad[#"artist"][#"name"] returns [NSNull null], artist.name gets assigned #""; otherwise, it gets assigned the value returned by ad[#"artist"][#"name"].
What I do is check before performing the assignment. The tripartite operator is a good shorthand way to do this. You have:
artist.name = ad[#"artist"][#"name"];
Instead, I would write
id val = ad[#"artist"][#"name"];
artist.name = val ? val : #""; // or val ?: #""
(I have not completely understood the question, so it might be necessary to break that check down into more stages or modify it in some way to check for [NSNull null] instead of nil. But you get the idea.)
Instead of subclassing NSDictionary to change the implementation of valueForKey:, I suggest you check if the key exists. So you would instead do this:
BOOL keyExistsInDictionary = [[myDict allKeys] containsObject:key];
id objectForKey = [myDict objectForKey:key];
if (nil == objectForKey && keyExistsInDictionary) objectForKey = #"";
If objectForKey: returns nil, you can check keyExistsInDictionary to see if the key is there and simply contains [NSNull null]. This lets you differentiate between a key that exists and a key that doesn't without modifying NSDictionary.
Can I create category that will extend - (id)valueForKey:(NSString *)key method
The short answer here is no, don't do that. Categories let you extend classes by adding method, but you shouldn't use them to override existing methods. The main reason for this is that it's unreliable -- the order in which the methods in categories are added to a class isn't defined, which means that if you have two categories that override the same method, you'd have no idea which version of the method the class would ultimately end up with.
Another reason is that you have no way to refer to the original method from the overriding method, so you'd have to re-implement the behavior that you want to keep.
Finally, a category changes the class, so every instance of that class will have the new behavior. That's not usually a problem when you're adding methods -- code that doesn't use the new methods remains unaffected by the new functionality. But when you change an existing method, particularly for a class that's as widely used as NSDictionary, it's very likely that you'll break a lot of existing code.
if the value of this key will be equal [NSNull null] it will return me empty string #""
You've gotten some good answers already, but since you asked about categories, here's an approach that solves the problem using a category appropriately. Instead of trying to change -valueForKey:, use a category to add a new method called -stringForKey: that returns a string. It sounds like you're working exclusively with strings, so this seems like a reasonable solution. The implementation can call -valueForKey: and transform non-string objects into strings:
#interface NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key;
#end
#implementation NSDictionary (StringValue)
- (NSString *)stringForKey:(NSString*)key
{
id value = [self valueForKey:key];
NSString *string;
if ([value isKindOfClass:[NSString class]]) {
string = (NSString*)value;
}
else if ([value isKindOfClass:[NSNull class]]) {
string = #"";
}
else {
string = [value description];
}
return string;
}
#end
I have an array of type NSArray as below:
#property (nonatomic, strong) NSArray *myArray;
In the setter of this array, I want to check if the array count is 0, then return nil else return the array as it is. I am doing it as below:
- (NSArray *)myArray
{
return ([self.myArray count] == 0) ? nil : self.myArray;
}
I am not getting any build error if I return nil for an NSArray, but its giving me run time error of bad access code. What mistake am I making here?
Your getter method is recursively calling itself, because
self.myArray
is translated by the compiler to
[self myArray]
This causes a stack overflow (!) eventually.
Inside a setter or getter method of a property, you have to access
the associated instance variable instead, e.g.
- (NSArray *)myArray
{
return ([_myArray count] == 0) ? nil : _myArray;
}
Do you instantiate the array anywhere else prior to calling this method? It will have to exist before you can access it.
_myArray = [NSArray arrayWithObjects:#"one",#"two",nil];
or one of the many other techniques to create an Array.