Cómo comparar la igualdad de estructuras / propiedades de dos NSObjects

I have a database model NSObject class with 100 or so properties -- mostly NSStrings but some NSDates. I'm trying to determine an easy way to compare any two given member objects of the database. For instance if user edits a property in objectA I want to be able to compare to the original object in the DB to see if changes were made.

[objectA isEqual:objectB] does not return valid results

so what I did is override the isEqual method in the class definition but it seems to be tedious to do. I also have to check each property to see if either one is Nil with dates since comparisons don't see to work if one of them is Nil.

preguntado el 01 de febrero de 12 a las 03:02

4 Respuestas

Maybe you can use KVC to make the isEqual override a little more concise.

First, you have to prepare an array of NSString that represents each property name, probably in init método.

- (id) init {

    // Other implementation of init

    self.propertyNames = [NSArray arrayWithObjects:@"property1",
                                                   @"property2",
                                                   @"property3",
                                                   ...
                                                   @"propertyN", nil];
}

In isEqual method, you go over all property names and get the property value using valueForKey método.

- (BOOL) isEqual : (id) object {
    for(NSString * propertyName in self.propertyNames) {
        id obj1 = [self valueForKey:property];
        id obj2 = [object valueForKey:property];

        if([obj1 isKindOfClass:NSStringFromClassName(@"NSString")]) {
            if(![obj2 isKindOfClass:NSStringFromClassName(@"NSString")])
                return NO;

            NSString * strObj1 = (NSString *)obj1;
            NSString * strObj2 = (NSString *)obj2;
            if(![strObj1 isEqualToString:strObj2])
                return NO;
        }

        if([obj1 isKindOfClass:NSStringFromClassName(@"NSDate")]) {
            if(![obj2 isKindOfClass:NSStringFromClassName(@"NSDate")])
                return NO;

            NSDate * dateObj1 = (NSDate *)obj1;
            NSDate * dateObj2 = (NSDate *)obj2;
            if(![dateObj1 isEqualToDate:dateObj2])
                return NO;
        }
    }
    return YES;
}

This way, you do not have to implement 100 of the same sequence.

The code above is very coarse (doesn't have null check. doesn't have class type check of the parameter object etc.), and not tested, but I hope you get the idea.

Another thing to note is that you have to override hash method as well. Following is the quote from the Documentación de NSObject

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.

Respondido 02 Feb 12, 05:02

Thank you, I'm going to add null checks and I think I'm set. Appreciate your help. - DoctorG

isEqual: compares pointers, meaning unless they are exactly the same object they will not be equal. What you want to do is use the compare: method, that will actually compare the contents of the NSStrings. Take a look at the documentación, there are several options you can specify for even case sensitive or not sensitive searching. NSDate has a similar method compare, but you can also do:

– isEqualToDate:
– earlierDate:
– laterDate:

Echa un vistazo a la documentación on that one too.

Edit:

NSStrings also have an isEqualToString: method so you can do:

[objectA isEqualToString:objectB]

That will return YES or NO depending on if the unicode for both objectA and objectB is exactly the same. This would be a better option if you were just testing for equality among exact copies, if you wanted to do comparison based on certain options like case sensitivity compare: es una mejor opción.

Respondido 01 Feb 12, 08:02

isEqual: works just fine between NSStrings, and does more than pointer comparison. It's only the NSObject implementation that does pointer comparison. - Homero BJ

There's a slightly less tedious way based on valueForKey:, but it has its own performance tradeoffs.

- (BOOL) isEqual: (id) obj
{
    if ([self class] != [obj class])
        return NO;

    NSArray *myProperties = ...;
    for (NSString *propertyName in myProperties) {
        if (![[self valueForKey:propertyName] isEqual: [obj valueForKey:propertyName]])
            return NO;
    }

    return YES;
}

Respondido 01 Feb 12, 09:02

Posting Working Code here just in case it will help others -- this code accounts for one or both objects being null (which will not compare using isEqual):

synthesize propertyNames in the Object Model: initialize once with array array of strings with ProperyNames:

-(void)initPropertyNamesArray {
    propertyNames = [[NSArray alloc] initWithObjects:
                     @"lastName",
                     @"firstName",
                     @"dob",
                     @"notes", ..., nil];
}

Override the isEqual: method (this example compares types of objects I'm using in my model -- strings, dates & images):

    - (BOOL) isEqual:(Patient *)otherPatient {
    if (!self.propertyNames) [self initPropertyNamesArray];
    BOOL showLog = YES;
    if (showLog) NSLog(@"Patient ISEQUAL");
    for(NSString *property in self.propertyNames) {
        id obj1 = [self valueForKey:property];
        id obj2 = [otherPatient valueForKey:property];
        if (showLog) NSLog(@"Comparing %@:", property);

         //CHECK IF ONE OR THE OTHER IS NULL -- the're not equal if so
        if (! (obj1 && obj2)) {
            if ((obj1 && !obj2) || (!obj1 && obj2)) {
                if (showLog) NSLog(@"%@ is null %@ is not!", obj1?@"TO:":@"FROM:", obj2?@"TO:":@"FROM:");
                return NO;
            }
        }

        //STRING
        if([obj1 isKindOfClass:[NSString class]]) {
            if(![obj2 isKindOfClass:[NSString class]])
                return NO;

            NSString * strObj1 = (NSString *)obj1;
            NSString * strObj2 = (NSString *)obj2;
            if (strObj1 && strObj2) { 
                if (![strObj1 isEqualToString:strObj2]) {
                    if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, strObj1, strObj2);
                    return NO;
                }
            } else if (! (!strObj1 && !strObj2)) { //one is nil one isn't NOT EQUAL
                if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, strObj1, strObj2);
                return NO; 
            } 
            //both nil (call them equal) 

        //DATE    
        } else if([obj1 isKindOfClass:[NSDate class]]) {
            if(![obj2 isKindOfClass:[NSDate class]])
                return NO;

            NSDate * dateObj1 = (NSDate *)obj1;
            NSDate * dateObj2 = (NSDate *)obj2;
            if (dateObj1 && dateObj2) { 
                if (![dateObj1 isEqualToDate:dateObj2]) {
                    if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, dateObj1, dateObj2);
                    return NO;
                }
            } else if (! (!dateObj1 && !dateObj2)) { //one is nil one isn't NOT EQUAL
                if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, dateObj1, dateObj2);
                return NO; 
            } 
            //both nil (call them equal)

        //IMAGE
        } else if([obj1 isKindOfClass:[UIImage class]] && ![property isEqual:@"lastUpdated"]) {
            if(![obj2 isKindOfClass:[UIImage class]])  
                return NO;

            UIImage * imgObj1 = (UIImage *)obj1;
            UIImage * imgObj2 = (UIImage *)obj2;
            if (imgObj1 && imgObj2) {  //check both not nil
                if(![imgObj1 isEqual:imgObj2]) {
                    if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, imgObj1, imgObj2);
                    return NO;
                }
            } else if (! (!imgObj1 && !imgObj2)) { //one is nil one isn't NOT EQUAL
                if (showLog) NSLog(@"%@ changed from:%@ to:%@", property, imgObj1, imgObj2);
                return NO; 
            } 
            //both nil (call them equal)
        }
        if (showLog) NSLog(@"OK!");
    }
    if (showLog) NSLog(@"PATIENTS ARE THE SAME!");
    return YES;
}

You can obviously take out all the NSLogs I put in for Debugging. Thanks to all who helped me out!

Respondido 13 Feb 12, 10:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.