Confused about the default isEqual and hash implements - ios

I know that I can override hash and isEqual to check 2 instances equality. Xcode has the default snippet and doucument https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectComparison.html as following
- (BOOL)isEqual:(id)other
{
if (other == self) {
return YES;
} else if (![super isEqual:other]) { //WHAT is this line mean ?
return NO;
} else {
return <#comparison expression#>;
}
}
- (NSUInteger)hash
{
return <#hash expression#>;
}
Okay,
other == self check two objects' pointers.
if ![super isEqual:other], what is this line means ? If super object is not equal other, return NO ? Then it will always return NO, the step 3 will not be executed.
Am I wrong ?
Thanks.

It's a typical implementation in a class-hierarchy, that is, if your class derives from a super class that has its own meaningful isEqual: implementation. In that case it is wise to let the super class test the equality of the common properties. If the common part is not equal, then there is no chance that the derived objects are equal.
It is not needed if you derive directly from NSObject.
Actually, you'll need an extra step as well:
- (BOOL)isEqual:(id)other
{
if (other == self) {
return YES;
} else if (![super isEqual:other]) {
return NO;
} else if (![other isKindOfClass:[MyClass class]]) {
return NO; // comparing incompatible objects
} else {
MyClass *myOther = (MyClass *) other;
return <#compare between self and myOther#>;
}
}

Let's look at one example of class inheritance:
#interface A : NSObject
#property (nonatomic, assign, readwrite) NSInteger fieldA;
#end
#interface B : A
#property (nonatomic, assign, readwrite) NSInteger fieldB;
#end
Now, if you want to implement equality on A, then you want to base it on the equality of fieldA:
// A equality
- (BOOL)isEqual:(id)other {
...
return [self fieldA] == [other fieldA];
}
When you are implementing equality on B, you need two conditions - first you have to make sure that fieldA is equal and then you have to make sure that fieldB is equal.
// B equality
- (BOOL)isEqual:(id)other {
...
return [super isEqual:other] && [self fieldB] == [other fieldB];
}
That's exactly what the [super isEqual:other] is doing - it checks the equality requirement of the superclass, that is fieldA.
To be honest, this isEqual: template is not very good. It is missing one of the most important things and that is the class equality check:
if (![other isMemberOfClass:[self class]]) {
return NO;
}
You don't need this check only when you never mix instances of different classes. However, when you start putting instances of A and B into the same array/dictionary etc. you will have crashes when trying to compare instances of A with instances of B.

There is a slightly different between Hash and isEqual in Objective-C.
First of all, NSObject checks equality with another object with the method isEqual: and basically, two objects may be equal to another, if they share a common set of observable properties.
Hashing in object comparison is an extra step in determining collection membership, which will faster your operation.
This will explain a little bit about hash and isEqual
Object equality is commutative ([a isEqual:b] --> [b isEqual:a])
If objects are equal, then their hash values must also be equal ([a isEqual:b] --> [a hash] == [b hash])
However, the converse does not hold: the hash values of two objects are equal do not mean that their values to be equal.
I hope this would be helpful. For reference, you can visit this link http://nshipster.com/equality/

Related

iOS: How to make factory method work with subclassing

I have a class of type Matrix which I'd like to subclass. Let's call my subclass Column. I'd like to add new properties to my subclass as well. I love factory methods, and don't like to repeat code, so I create a factory method in my subclass which calls its superclass' factory method when initializing an object.
The problem is that if I create an instance of my Column class, and call a property only found in Column, I get an error, because my Column class' factory method which calls my Matrix class' factory method is returning an instance of type Matrix instead of Column or whatever subclass calls it. I understand why this happens, but I'm not sure how to fix this.
I've read the following article on factory methods, and know that instead of using [[Matrix alloc] init]; I should be using [[self alloc] init];. The problem is I'm not sure how I'd access my instance variables like matrix, rows and freeData which I define in my header as follows:
#interface Matrix : NSObject <NSCoding, NSCopying> {
#public double *matrix;
#public int rows;
#public int columns;
#private BOOL freeUpData;
}
Here's the factory method I need help in. How can I rewrite it so it can access my instance variables, and also work with subclassing (and not only return a Matrix instance).
+ (instancetype)matrixFromArray:(double *)arr Rows:(int)m Columns:(int)n Mode:(refMode)mode
{
Matrix *mt = [[Matrix alloc] init];
if (mode == YCMCopy) {
double *new_m = malloc(m*n*sizeof(double));
memcpy(new_m, arr, m*n*sizeof(double));
mt->matrix = new_m;
mt->freeData = YES;
}
else {
mt->matrix = arr;
mt->freeData = NO;
}
if (mode != YCMWeak) {
mt->freeData = YES;
}
mt->rows = m;
mt->columns = n;
return mt;
}
You always create Matrix instance
but you need to create instance of current class
+ (instancetype)matrix
{
Matrix *mt = [[self alloc] init];
....
return mt;
}
And if you do that
[Matrix matrix] will return Matrix object
[Column matrix] will return Column object

Compare 2 Objects in Objective-C

In my application, I want to compare 2 core data instances of the entity "Workout". I want to check if the 2 objects have identical attribute values for all of their attributes. Essentially if the two objects are the same minus the relationship, whosWorkout. Is there any way to do this without manually checking every single attribute? I know I could do:
if(object1.intAttr == object2.intAttr){
NSLog(#"This attribute is the same");
}
else{
return;
}
repeat with different attributes...
Is there any core data method to make this a bit less tedious?
First I would create an isEqual method in the Workout subclass like this...
-(BOOL)isEqualToWorkout:(Workout*)otherWorkout
{
return [self.attribute1 isEqual:otherWorkout.attribute1]
&& [self.attribute2 isEqual:otherWorkout.attribute2]
&& [self.attribute3 isEqual:otherWorkout.attribute3]
&& [self.attribute4 isEqual:otherWorkout.attribute4]
...;
}
Then whenever you want to compare to Workout objects just use...
BOOL equal = [workout1 isEqualToWorkout:workout2];
You can iterate through the attributes by name.
for (NSString *attribute in object.entity.attributesByName) {
if ([[object valueForKey:attribute] intValue] !=
[[object2 valueForKey:attribute] intValue]) {
return NO;
}
}
return YES;
This assumes all integer attributes. You could do a switch statement to check for the class with the class method and deal with different data types as well.
If you need to compare whether one object represents a greater or lesser value than another object, you can’t use the standard C comparison operators > and <. Instead, the basic Foundation types, like NSNumber, NSString and NSDate, provide a compare: method:
if ([someDate compare:anotherDate] == NSOrderedAscending) {
// someDate is earlier than anotherDate
}
I ended up doing the following:
-(BOOL)areEqual:(Workout *)firstWorkout secondWorkout:(Workout *)secondWorkout{
NSArray *allAttributeKeys = [[[firstWorkout entity] attributesByName] allKeys];
if([[firstWorkout entity] isEqual:[secondWorkout entity]]
&& [[firstWorkout committedValuesForKeys:allAttributeKeys] isEqual:[secondWorkout committedValuesForKeys:allAttributeKeys]]) {
return YES;
}
else{
return NO;
}
}

What is the Objective-C equivalent of Java BeanUtils.copyProperties? [duplicate]

I would like to know if they have an equivalent in Objective C of the JAVA's methode "BeanUtils.CopyProperties(bean1, Bean2);" ?
Or other solution, i would like to cast motherObject to childObject :
#interface motherBean : NSObject{ ...}
#interface childBean : motherBean { ...}
motherBean m = [motherBean new];
childBean f = m;
With the first tests it's work but I have a warning : "incompatible pointer types returning ...";
I use WSDL2Objc and it generate bean, and the name of it can change between 2 generation :-/
I prefere to work with the child and just change the name in her definition
Thanks
Anthony
Take a look at commons-beanutils package. It has lots of property method for you to copy stuff. In particular:
PropertyUtils.copyProperties(bean1, bean2);
However, as to your second question, you're trying to downcast an instance of a parent class to a child class?
I'm not sure how that would be legal in any OO language. Sure you can forcibly cast:
// This is not legal because you can't case from one class to anther
// unless the actual instance type (not the declared type of the variable,
// but the constructed type) is either the casted class or a subclass.
Parent p = new Parent();
Child c = (Child) p;
But you'd get a ClassCastException , since you can't treat an instance of a parent class as if it were a child class (only the other way around). Either of these would be legal however:
// This is legal because you're upcasting, which is fine
Child c = new Child();
Parent p = c;
// This is legal because the instance "p" is actually an
// instance of the "Child" class, so the downcast is legal.
Parent p = new Child();
Child c = (Child) p;
To answer your first question, you could easily write the code to copy property values between instances. It is easiest if you restrict properties to proper Objective-C properties (items declared using #property()) which is probably the best practice anyway. You can use the Objective-C runtime functions to get the list of all properties on a class (class_getPropertyList) and call property_getName() to get the property's name and call property_getAttributes() to make sure it is writeable. Then you can use NSObject's Key Value Coding to get and set the property values using valueForKeyPath: and setValueForKeyPath: respectively.
Some problems with you code example are that instances should be pointers. Second, you need an explicit cast since you are assigning an instance of a class to its super class. The reverse would not require a cast. That is probably why you are getting the warning.
The method BeanUtils.copyProperties
//.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface BeanUtils : NSObject
+(void)copyProperties:(id)src dest:(id)dest;
#end
//.m
#import "BeanUtils.h"
#implementation BeanUtils
+(void)copyProperties:(id)src dest:(id)dest {
NSLog(#"classeSrc=%# dst=%#", [src class], [dest class]);
if(src == NULL || dest == NULL) {
return;
}
Class clazz = [src class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++)
{
NSString *propertyName = [NSString stringWithUTF8String:property_getName(properties[i])];
[propertyArray addObject:propertyName];
//on verifie que la prop existe dans la classe dest
objc_property_t prop = class_getProperty([dest class], [propertyName UTF8String]);
if(prop != NULL) {
id result = [src valueForKey:propertyName];
[dest setValue:result forKey: propertyName];
}
else {
[NSException raise:NSInternalInconsistencyException format:#"La propriété %# n'existe pas dans la classe %#",propertyName, [dest class] ];
}
}
free(properties);
}
#end
call :
EleveBean *eleveBean = [EleveBean new];
eleveBean.nom = #"bob";
eleveBean.prenom = #"john";
tns1_EleveBean *tnsEleve = [tns1_EleveBean new];
[BeanUtils copyProperties:eleveBean dest:tnsEleve];
STAssertEquals(eleveBean.nom, tnsEleve.nom, #"");
STAssertEquals(eleveBean.prenom, tnsEleve.prenom, #"");

How to check at runtime if a property was declared #dynamic

I'm working on a dynamic implementation of a dictionary that also supports properties declared using the #dynamic keyword (similar to NSManagedObject).
Can I tell at runtime if a particular selector was declared with #dynamic? Is this just compiler trickery for design time tooling and lost at runtime or is there someway to inspect this?
+ (BOOL) resolveInstanceMethod:(SEL)sel
{
NSString *method = NSStringFromSelector(sel);
// ideally I could also check here if the selector is #dynamic
if ([method hasPrefix:#"set"] && [method rangeOfString:#":"].location == method.length -1) {
class_addMethod([self class], sel, (IMP) dynamicSet, "v#:#");
return YES;
}
else if ([method hasPrefix:#"get"] && [method rangeOfString:#":"].location == method.length -1) {
class_addMethod([self class], sel, (IMP) dynamicGet, "v#:#");
return YES;
}
BOOL value = [super resolveInstanceMethod:sel];
return value;
}
Also, my class subclasses NSDictionary but it when [super resolveInstanceMethod:sel] is called for an existing method - it still returns false?
If you know the name of the property you can use some runtime functions to investigate whether it's a dynamic property or not, as shown in the following function. Make sure to import <objc/runtime.h>.
BOOL isClassPropertyDynamic(Class theClass, NSString *propertyName)
{
BOOL isDynamic = NO;
objc_property_t property = class_getProperty(theClass, [propertyName UTF8String]);
char *dynamicAttributeValue = property_copyAttributeValue(property, "D");
if (dynamicAttributeValue != NULL) {
isDynamic = YES;
free(dynamicAttributeValue);
}
return isDynamic;
}
However, it's not always going to be easy to go from a selector name to the property, as both getters and setters names can be customized at declaration time. Typically that is only done for getters of boolean properties but technically anyone can break that convention.
Conventionally, if a selector starts with "set" followed by an uppercase letter and contains one ":" at the end, the property name would be the string resulting from removing "set" and ":" and making the first letter lowercase.
If a selector starts with "is" followed by an uppercase letter and has no arguments, then the property name corresponding to that would be the string resulting from removing "is" and making the first letter lowercase. Selectors that have no arguments and don't start with "is" and an uppercase letter would generally have the property name and the selector name the same.
Again, that's just convention and will be broken by somebody somewhere. So, you have to decide if it's truly valuable to determine whether a selector corresponds to a dynamic property or not (like borrrden I doubt it's really relevant but I'm not familiar with your requirement).
You could also follow rob mayoff's excellent suggestion from the comments that you "iterate over all of the properties (using class_copyPropertyList ) and check the G and S (attributes) of each" to build a mapping between selectors and properties.

creating default vars (objects) in objective c

dumb question: lets say I'm assigning a var in a conditional statement. I don't know if the condition will be satisfied and i still want the var to be defined.. whats the correct way of writing this
example:
NSDecimalNumber *number = [[NSDecimalNumber alloc]init]; // this is pointless right?
if(x == z){
number = [whatevernum1 decimalNumberByMultiplyingBy: whatevernum2];
} else {
number = [whatevernum2 decimalNumberByDividingBy: whatevernum3];
}
// do something with number variable.
There is no need to initialize number since it will be set. Just do this:
NSDecimalNumber *number;
if(x == z){
number = [whatevernum1 decimalNumberByMultiplying: whatevernum2];
} else {
number = [whatevernum2 decimalNumberByDividing: whatevernum3];
}
// do something with number variable.
In your case number will be assigned a value one way or another. But you might have a situation like this:
if (someCondition) {
// set number to value A
} else if (anotherCondition) {
// set number to value B
}
Here, it is possible that neither condition is met. In this case you need to deal with this properly by initializing number to nil.
NSDecimalNumber *number = nil;
if (someCondition) {
// set number to value A
} else if (anotherCondition) {
// set number to value B
}
if (number) {
// process result
}
You need to declare the variable but not assign it, like this:
NSDecimalNumber *number;
if(x == z){
number = [whatevernum1 decimalNumberByMultiplying: whatevernum2];
} else {
number = [whatevernum2 decimalNumberByDividing: whatevernum3];
}
This tells the compiler that you want to use a variable named number, but don't have a value for it yet. In some cases, you may find it convenient to initialise the variable to nil rather than leaving it as a null pointer.
Normally, as others have pointed out, you would either not initialise (if you can guarantee that you will set a value, eg through an if/else pair), or you would initialise to nil.
In this simple case, a ternary statement would make your code much clearer:
NSDecimalNumber *number = x == z ? [whatevernum1 decimalNumberByMultiplyingBy:whatevernum2] : [whatevernum2 decimalNumberByDividingBy:whatevernum3];

Resources