Как зациклить атрибут в NSObject? - PullRequest
0 голосов
/ 07 февраля 2012

Например, объект выглядит примерно так:

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

, и я хотел бы сравнить с пользователем, если его атрибуты одинаковы, я буду относиться к нему как к равному ... вместо записистатический метод для сравнения достаточного количества атрибутов один за другим, могу ли я получить ленивый способ получить все атрибуты для сравнения, спасибо.?

1 Ответ

0 голосов
/ 07 февраля 2012

Для сравнения, это то, что вы пытаетесь избежать написания.

-(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 - это чрезвычайно простой способ объединения хэшей, и я в основном использую его как замену.Это может повлиять на качество хеш-значения в зависимости от распределения базовых хеш-функций.Если хеши имеют равномерное распределение, все должно быть в порядке.Также обратите внимание, что объединение хэшей работает только потому, что строки NSS, равные по содержанию, имеют одинаковые хэшиЭтот подход не будет работать со всеми типами;в частности, он не будет работать с типами, которые используют реализацию по умолчанию hash.

. Чтобы обойти написанное выше, сначала измените тип свойства age на NSNumber, чтобыне должен рассматриваться как особый случай.Вам не нужно менять ивар, хотя вы можете, если хотите.

@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];
}

Во-вторых, убедитесь, что ваш класс поддерживает протокол быстрого перечисления .Если это не так, вы можете реализовать -countByEnumeratingWithState:objects:count:, используя отражение (с функциями времени выполнения Objective C), чтобы получить список свойств для экземпляров вашего класса.Например (взято частично из " Реализация 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: Чтобы быть истиной, когда два объекта имеют одинаковые значения свойств, схема хеширования должна прямо или косвенно зависеть от состояния объекта, поэтому от этой опасности не обойтись.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...