如何在NSObject中循环回属性?

时间:2012-02-07 01:42:08

标签: objective-c

例如,Object就像这样:

MyUser: NSObject{
   NSString *firstName;
   NSString *lastName;
   NSString *gender;
   int       age;
}

我希望与用户比较,如果他们的属性相同,我会把它视为相等...而不是写一个静态方法来逐个比较足够的属性,我可以有一个懒惰的方式来获取所有要比较的属性,谢谢。?

1 个答案:

答案 0 :(得分:-1)

为了进行比较,这是你试图避免写作的内容。

-(NSUInteger)hash {
    return [firstName hash] ^ [lastName hash] ^ [gender hash] ^ age;
}
-(BOOL)isEqual:(id)other {
    return [other isKindOfClass:[self class]]
        && age == other.age
        && [gender isEqualToString:other.gender]
        && [firstName isEqualToString:other.firstName]
        && [lastName isEqualToString:other.lastName];
}

使用XOR是一种非常简单的组合哈希的方法,我主要将其作为替身。它可能会损害哈希值的质量,具体取决于底层哈希函数的分布。如果哈希分布均匀,那就应该没问题了。另请注意,组合哈希仅起作用,因为内容相同的NSStrings具有相同的哈希值。这种方法不适用于所有类型;特别是,它不适用于使用hash的默认实现的类型。

为了解决上述问题,首先将age属性的类型更改为NSNumber,因此不必将其作为特殊情况处理。您不必更改ivar,但如果您愿意,也可以。

@interface MyUser : NSObject {
    ...
    unsigned int age; // Or just make this an NSNumber*
}
...
@property (assign,nonatomic) NSNumber *age;

@implementation MyUser
@synthesize firstName, lastName, gender;
/* if the age ivar is an NSNumber*, the age property can be synthesized
   instead of explicitly defining accessors.
 */
@dynamic age;

-(NSNumber*)age {
    return [NSNumber numberWithUnsignedInt:age];
}
-(void)setAge:(NSNumber*)newAge {
    age = [newAge unsignedIntValue];
}

其次,确保您的班级支持fast enumeration protocol。如果没有,您可以通过使用反射(使用Objective-C运行时函数)来实现-countByEnumeratingWithState:objects:count:,以获得类的实例list of properties。例如(部分来自Cocoa With Love的“Implementing countByEnumeratingWithState:objects:count:”):

#import <objc/runtime.h>
...

@interface MyUser (NSFastEnumeration) <NSFastEnumeration>
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
@end


@implementation MyUser
@synthesize firstName, lastName, gender;

/* defined in the main implementation rather than a category, since there
   can be only one +[MyUser initialize].
 */
static NSString **propertyNames=0;
static unsigned int cProperties=0;

+(void)initialize {
    unsigned int i;
    const char *propertyName;
    objc_property_t *properties = class_copyPropertyList([self class], &cProperties);

    if ((propertyNames = malloc(cProperties * sizeof(*propertyNames)))) {
        for (i=0; i < cProperties; ++i) {
            propertyName = property_getName(properties[i]);
            propertyNames[i] = [[NSString alloc]
                                   initWithCString:propertyName
                                          encoding:NSASCIIStringEncoding];
        }
    } else {
        cProperties = 0;
        // Can't initialize property names. Fast enumeration won't work. What do?
    }
}
...
@end

@implementation MyUser (NSFastEnumeration)
-(NSUInteger)
countByEnumeratingWithState:(NSFastEnumerationState *)state
                    objects:(id *)stackbuf 
                      count:(NSUInteger)len
{
    if (state->state >= cProperties) {
        return 0;
    }

    state->itemsPtr = propertyNames;
    state->state = cProperties;
    state->mutationsPtr = (unsigned long *)self;

    return cProperties;
}
@end

最后,实施hash(使用快速枚举)和isEqual:。哈希应该计算所有属性的哈希值,然后将它们组合起来为MyUser实例创建哈希值。 isEqual:可以简单地检查另一个对象是MyUser(或其子类)的实例并比较哈希值。例如:

-(NSUInteger)hash {
    NSUInteger myHash=0;
    for (NSString *property in self) {
        // Note: extremely simple way of combining hashes. Will likely lead
        // to bugs
        myHash ^= [[self valueForKey:property] hash];
    }
    return myHash;
}
-(BOOL)isEqual:(id)other {
    return [other isKindOfClass:[self class]]
        && [self hash] == [other hash];
}

现在,问问自己哪个整体工作较少。如果你想要一个适用于你所有类的单一方法,它可能是第二个(进行一些更改,例如将+initialize转换为NSObject上的类方法,它返回属性名称数组和长度),但很可能前者是赢家。

上述两个hash实现中存在基于属性值计算哈希的危险。来自Apple关于hash的文档:

  

如果将可变对象添加到使用哈希值来确定对象在集合中的位置的集合中,则对象的哈希方法返回的值在对象位于集合中时不得更改。因此,散列方法必须不依赖于任何对象的内部状态信息,或者必须确保在对象位于集合中时对象的内部状态信息不会更改。

由于只要两个对象具有相同的属性值,您希望isEqual:为真,散列方案必须直接或间接地依赖于对象的状态,因此无法避免这种危险。