Примечание: Следующие вопросы SO связаны, но ни они, ни связанные ресурсы, кажется, не отвечают полностью на мои вопросы, особенно в отношении реализации тестов на равенство для наборов объектов .
Фон
NSObject предоставляет по умолчанию реализации -hash
(который возвращает адрес экземпляра, например (NSUInteger)self
) и -isEqual:
(который возвращает NO
, если только адреса получателя и параметра не совпадают). Эти методы предназначены для переопределения по мере необходимости, но в документации ясно, что вы должны предоставить оба или ни того, ни другого. Кроме того, если -isEqual:
возвращает YES
для двух объектов, то результат -hash
для этих объектов должен быть одинаковым. В противном случае могут возникнуть проблемы, когда объекты, которые должны быть одинаковыми - например, два строковых экземпляра, для которых -compare:
возвращает NSOrderedSame
- добавляются в коллекцию Какао или сравниваются напрямую.
Контекст
Я разрабатываю CHDataStructures.framework , библиотеку структур данных Objective-C с открытым исходным кодом. Я реализовал несколько коллекций и в настоящее время совершенствую и улучшаю их функциональность. Одной из функций, которые я хочу добавить, является возможность сравнивать коллекции на предмет равенства.
Вместо сравнения только адресов памяти эти сравнения должны учитывать объекты, присутствующие в двух коллекциях (включая упорядочение, если применимо). Этот подход имеет довольно прецедент в Какао, и, как правило, использует отдельный метод, включая следующие:
Я хочу сделать свои собственные коллекции устойчивыми к тестам на равенство, чтобы их можно было безопасно (и предсказуемо) добавлять в другие коллекции и позволять другим (например, NSSet) определять, равны ли две коллекции / эквивалентны / дубликаты.
Проблемы
Метод -isEqualTo...:
прекрасно работает сам по себе, но классы, которые определяют эти методы, обычно также переопределяют -isEqual:
для вызова [self isEqualTo...:]
, если параметр того же класса (или, возможно, подкласса), что и получатель, или [super isEqual:]
иначе. Это означает, что класс также должен определять -hash
, чтобы он возвращал одно и то же значение для разнородных экземпляров, имеющих одинаковое содержимое.
Кроме того, документация Apple для -hash
предусматривает следующее: (выделено мной)
"Если изменяемый объект добавляется в коллекцию, которая использует хеш-значения для определения позиции объекта в коллекции, значение, возвращаемое методом хеш-функции объекта, не должно изменяться, пока объект находится в коллекции. Следовательно, либо метод хеширования не должен полагаться на какую-либо внутреннюю информацию о состоянии объекта или . Необходимо убедиться, что информация о внутреннем состоянии объекта не изменяется, пока объект находится в коллекции. Таким образом, например, изменяемый словарь может быть помещен в хеш-таблицу, но вы не должны изменять его, пока он там (обратите внимание, что может быть трудно узнать, находится ли данный объект в коллекции.) "* ** 1104 1105 *
Редактировать: Я определенно понимаю, почему это необходимо, и полностью согласен с аргументацией - я упомянул это здесь, чтобы предоставить дополнительный контекст, и обошел тему, почему это так, ради краткость.
Все мои коллекции являются изменяемыми, и хэш должен учитывать не менее некоторых содержимого, поэтому единственный вариант здесь - считать это ошибкой программирования для изменения коллекции, хранящейся в другой коллекции. (Все мои коллекции принимают NSCopying , поэтому такие коллекции, как NSDictionary могут успешно сделать копию для использования в качестве ключа и т. Д.)
Для меня имеет смысл реализовать -isEqual:
и -hash
, поскольку (например) косвенный пользователь одного из моих классов может не знать, какой конкретный метод -isEqualTo...:
вызывать, или даже заботиться о том, являются ли два объекта экземпляры того же класса. Они должны иметь возможность вызывать -isEqual:
или -hash
для любой переменной типа id
и получать ожидаемый результат.
В отличие от -isEqual:
(который имеет доступ к двум сравниваемым экземплярам), -hash
должен возвращать результат «вслепую», имея доступ только к данным в конкретном экземпляре. Так как он не может знать, для чего используется хеш, результат должен быть согласован для всех возможных случаев, которые следует считать равными / идентичными, и всегда должны согласовываться с -isEqual:
. (Редактировать: это было опровергнуто ответами ниже, и это, безусловно, облегчает жизнь.) Кроме того, написание хороших хеш-функций нетривиально - гарантировать уникальность - непростая задача, особенно если у вас только NSUInteger (32/64 бита) для его представления.
Вопросы
- Существуют ли передовые практики при реализации сравнений на равенство
-hash
для коллекций?
- Есть ли какие-то особенности для планирования в коллекциях Objective-C и Cocoa-esque?
- Есть ли хорошие подходы для модульного тестирования
-hash
с достаточной степенью достоверности?
- Любые предложения по реализации
-hash
для согласования с -isEqual:
для коллекций, содержащих элементы произвольных типов? О каких подводных камнях я должен знать? ( Редактировать: Не так проблематично, как я думал в первый раз - как указывает @ kperryua , "равные -hash
значения делают не подразумевают -isEqual:
".)
Редактировать: Я должен был уточнить, что меня не смущает вопрос о том, как реализовать -isEqual: или -isEqualTo ...: для коллекций, это просто. Я думаю, что моя путаница возникла главным образом из-за (ошибочного) мнения, что -hash ДОЛЖЕН вернуть другое значение, если -isEqual: возвращает NO. Сделав криптографию в прошлом, я подумал, что хэши для разных значений ДОЛЖНЫ быть разными. Тем не менее, ответы ниже заставили меня понять, что «хорошая» хеш-функция на самом деле сводится к минимизации коллизий сегментов и цепочки для коллекций, которые используют -hash
. Хотя уникальные хеши предпочтительнее, они не являются строгим требованием.