如何比较两个 NSObject 的结构/属性的相等性

发布于 2024-12-29 23:19:38 字数 311 浏览 1 评论 0原文

我有一个数据库模型 NSObject 类,具有 100 个左右的属性——主要是 NSString,但也有一些 NSDate。 我试图确定一种简单的方法来比较数据库的任何两个给定成员对象。例如,如果用户编辑 objectA 中的属性,我希望能够与数据库中的原始对象进行比较,以查看是否进行了更改。

[objectA isEqual:objectB] 不返回有效结果

,所以我所做的是重写类定义中的 isEqual 方法,但这似乎很乏味。我还必须检查每个属性,看看其中一个属性是否为 Nil(带有日期),因为如果其中之一为 Nil,则比较不会起作用。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

谁与争疯 2025-01-05 23:19:38

也许您可以使用 KVC 使 isEqual 重写更加简洁。

首先,您必须准备一个代表每个属性名称的 NSString 数组,可能在 init 方法中。

- (id) init {

    // Other implementation of init

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

isEqual 方法中,您可以检查所有属性名称并使用 valueForKey 方法获取属性值。

- (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;
}

这样,您就不必执行 100 个相同的序列。

上面的代码非常粗糙(没有空检查。没有参数对象的类类型检查等),并且没有经过测试,但我希望你明白。

另一件需要注意的事情是,您还必须覆盖 hash 方法。以下是 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.

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 method.

- (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 method.

- (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 NSObject 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.
望喜 2025-01-05 23:19:38

isEqual:比较指针,这意味着除非它们是完全相同的对象,否则它们将不相等。您想要做的是使用 compare: 方法,它实际上会比较 NSStrings 的内容。查看文档,您可以为区分大小写或不区分大小写的搜索指定多个选项。 NSDate 有一个类似的方法比较,但你也可以这样做:

– isEqualToDate:
– earlierDate:
– laterDate:

看看 文档 也有关于该文档。

编辑:

NSStrings 还有一个 isEqualToString: 方法,因此您可以执行以下操作:

[objectA isEqualToString:objectB]

这将返回 YES 或 NO,具体取决于 objectA 和 objectB 的 unicode 是否完全相同。如果您只是测试精确副本之间的相等性,如果您想基于某些选项(例如区分大小写)进行比较,这将是一个更好的选择 compare: 是一个更好的选择。

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 documentation, 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:

Take a look at the documentation 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: is a better choice.

青衫负雪 2025-01-05 23:19:38

有一种基于 valueForKey: 的稍微不那么繁琐的方法,但它有自己的性能权衡。

- (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;
}

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;
}
伊面 2025-01-05 23:19:38

在这里发布工作代码以防万一它会帮助其他人——此代码说明一个或两个对象为空(不会使用 isEqual 进行比较):

在对象模型中合成 propertyNames:
使用 ProperyNames 的字符串数组初始化一次:

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

覆盖 isEqual: 方法
(此示例比较了我在模型中使用的对象类型 - 字符串、日期和图像):

    - (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;
}

您显然可以取出我放入用于调试的所有 NSLog。
感谢所有帮助我的人!

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!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文