Что работает быстрее — поиск по NSArray или NSSet?


В Objective-C (и Swift при использовании Foundation) коллекции NSArray и NSSet — это два разных типа контейнеров, предназначенных для хранения объектов, но с разными внутренними устройствами, семантикой доступа и производительностью при выполнении операций, особенно поиска.**

Ключевое различие между ними заключается в структуре хранения данных и в времени доступа к элементам.

1. NSArray — упорядоченная коллекция

Что это такое:

NSArray — это упорядоченный список объектов, аналог обычного массива. Элементы в NSArray располагаются в том порядке, в котором они были добавлены.

Внутреннее устройство:

  • **Реализован как динамический массив указателей.
    **
  • **Каждый элемент хранится в определённой позиции.
    **
  • **Быстрый доступ по индексу (O(1)).
    **
  • **Поиск по значению осуществляется линейно — O(n).
    **

Операции поиска:

Когда вы ищете объект в NSArray, используется метод containsObject: или indexOfObject:, который последовательно перебирает массив, сравнивая элементы с искомым.

Пример:

**NSArray \*array = @\[@"apple", @"banana", @"cherry"\];**
**BOOL exists = \[array containsObject:@"banana"\];**

Этот поиск — линейный: в худшем случае будет пройден весь массив.

2. NSSet — неупорядоченное множество

Что это такое:

NSSet — это неупорядоченное множество уникальных объектов, аналог математического множества.

Внутреннее устройство:

  • **Основан на хэш-таблице.
    **
  • **Каждый объект хэшируется, и по его хэшу определяется место хранения.
    **
  • **Добавление и поиск — амортизированное O(1).
    **
  • **Элементы не имеют порядка.
    **
  • **Не допускает дубликатов.
    **

Операции поиска:

Поиск в NSSet происходит через хеширование — то есть почти мгновенно, если реализован -hash и -isEqual: у объектов правильно.

Пример:

**NSSet \*set = \[NSSet setWithObjects:@"apple", @"banana", @"cherry", nil\];**
**BOOL exists = \[set containsObject:@"banana"\];**

Здесь поиск будет выполнен за постоянное время — O(1) в среднем случае, потому что NSSet использует хэш-таблицу.

3. Сравнение производительности поиска

Операция NSArray NSSet
Поиск (containsObject:) O(n) (линейный) O(1) (почти мгновенно)
--- --- ---
Добавление O(1) в конец O(1) в среднем
--- --- ---
Доступ по индексу O(1) ✘ (не поддерживается)
--- --- ---
Удаление O(n) по значению O(1) в среднем
--- --- ---
Упорядоченность Да Нет
--- --- ---
Поддержка дубликатов Да Нет
--- --- ---

Таким образом, поиск по NSSet почти всегда быстрее, особенно при большом объёме данных.

4. Практический пример и сравнение времени

**NSMutableArray \*array = \[NSMutableArray array\];**
**NSMutableSet \*set = \[NSMutableSet set\];**
**NSString \*target = @"item9999";**
**for (int i = 0; i < 10000; i++) {**
**NSString \*str = \[NSString stringWithFormat:@"item%d", i\];**
**\[array addObject:str\];**
**\[set addObject:str\];**
**}**
**NSDate \*start = \[NSDate date\];**
**\[array containsObject:target\];**
**NSTimeInterval arrayTime = \[\[NSDate date\] timeIntervalSinceDate:start\];**
**start = \[NSDate date\];**
**\[set containsObject:target\];**
**NSTimeInterval setTime = \[\[NSDate date\] timeIntervalSinceDate:start\];**
**NSLog(@"NSArray time: %f", arrayTime);**
**NSLog(@"NSSet time: %f", setTime);**

Результат почти всегда покажет, что поиск по NSSet будет в десятки или сотни раз быстрее, особенно при больших объёмах.

5. Почему NSSet быстрее

Потому что:

  • **Используется хэш-функция для быстрого определения местоположения объекта;
    **
  • **Поиск не требует прохода по всем элементам;
    **
  • **Наличие ключевых методов -hash и -isEqual: позволяет NSSet оптимизировать сравнение;
    **
  • **Не требует сортировки или проверки порядка.
    **

В то время как NSArray не может избежать последовательного сравнения каждого элемента, особенно если объект — пользовательский и не поддерживает прямое сравнение указателей.

6. Особенности и ограничения

6.1. NSSet требует корректной реализации hash и isEqual:

Если вы используете собственные объекты, они должны корректно реализовать:

**\- (NSUInteger)hash {**
**return \[self.identifier hash\];**
**}**
**\- (BOOL)isEqual:(id)object {**
**if (!\[object isKindOfClass:\[MyClass class\]\]) return NO;**
**return \[self.identifier isEqualToString:((MyClass \*)object).identifier\];**
**}**

Без этого NSSet не сможет корректно отличать уникальные значения и выполнять быстрый поиск.

6.2. Нет порядка в NSSet

Если вам нужен порядок следования элементов, используйте NSArray или NSOrderedSet.

NSSet не сохраняет порядок:

**NSSet \*set = \[NSSet setWithObjects:@"a", @"b", @"c", nil\];**
**NSLog(@"%@", set); // порядок может быть "b, a, c"**

6.3. Уникальность объектов

NSSet не допускает дубликатов. При добавлении двух одинаковых объектов (с точки зрения isEqual:) второй будет проигнорирован.

NSArray, напротив, может содержать повторяющиеся элементы.

6.4. Поддержка в Swift

В Swift эквиваленты:

- **NSArray  Array  
    **
- **NSSet  Set**

И та же логика применима:

**let arr = \["a", "b", "c", "d"\]**
**arr.contains("c") // O(n)**
**let set: Set = \["a", "b", "c", "d"\]**
**set.contains("c") // O(1)**

Swift Set работает аналогично NSSet, но типобезопасен и работает быстрее за счёт компиляции без рантайма Objective-C.

7. Когда выбирать NSArray, когда NSSet

Сценарий Коллекция
Важен порядок элементов NSArray
--- ---
Нужны дубликаты NSArray
--- ---
Часто обращаетесь по индексу NSArray
--- ---
Часто ищете, проверяете наличие элемента NSSet
--- ---
Не нужны дубликаты NSSet
--- ---
Часто выполняете множественные пересечения NSSet
--- ---

8. Альтернативы: NSOrderedSet

Если нужно и уникальность, и сохранение порядка, можно использовать NSOrderedSet — это структура, совмещающая особенности NSArray и NSSet:

  • **Уникальные элементы (как NSSet);
    **
  • **Фиксированный порядок (как NSArray);
    **
  • **Поиск по значению — быстрее, чем в массиве.
    **

Однако NSOrderedSet менее производителен, чем чистый NSSet для поиска.

**