Необязательный массив против пустого массива в Swift

У меня есть простой класс Person в Swift, который выглядит примерно так:

class Person {
    var name = "John Doe"
    var age = 18
    var children = [Person]?

    \\ init function goes here, but does not initialize children array
}

Вместо объявления children элементов в качестве необязательного массива я мог бы просто объявить его и инициализировать его как пустой массив следующим образом:

var children = [Person]()

Я пытаюсь решить, какой подход лучше. Объявление массива как необязательного массива означает, что он не будет занимать какую-либо память вообще, тогда как пустой массив имеет по крайней мере некоторую память, выделенную для него, правильно? Таким образом, использование необязательного массива означает, что будет по крайней мере некоторое сохранение памяти. Я предполагаю, что мой первый вопрос: действительно ли существует реальная экономия памяти, или мои предположения об этом неправильны?

С другой стороны, если это не является обязательным, то каждый раз, когда я пытаюсь использовать его я должен проверить, чтобы увидеть, если это nil или нет, прежде чем добавлять или удалять объекты из него. Так что там будет некоторая потеря эффективности (но не так много, я думаю).

Я вроде как факультативный подход. Не у каждого Person будут дети, так почему бы не позволить children быть nil пока Person решит поселиться и воспитывать семью?

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

Ответ 1

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

Я бы выбрал:

  • Пустой массив, если список может быть пустым, но это переходный статус, и в конце он должен иметь хотя бы один элемент. Будучи не факультативным, ясно, что массив не должен быть пустым
  • Необязательно, если возможно, чтобы список был пустым для всего жизненного цикла объекта контейнера. Будучи необязательным, становится ясно, что массив может быть пустым

Позвольте мне привести несколько примеров:

  • Заказ на поставку с мастером и деталями (одна деталь на продукт): заказ на поставку может содержать 0 деталей, но это переходный статус, потому что не имеет смысла иметь заказ на поставку с 0 продуктами
  • Человек с детьми: у человека не может быть детей на всю жизнь. Это не временный статус (хотя и не постоянный), но, используя необязательное, ясно, что для человека не имеет детей.

Обратите внимание, что мое мнение заключается только в том, чтобы сделать код более понятным и самообучающимся - я не думаю, что есть какие-либо существенные различия в производительности, использовании памяти и т.д. Для выбора одного или другого варианта.

Ответ 2

Я собираюсь сделать противоположный случай из Йорди - пустой массив, точно так же ясно говорит: "У этого человека нет детей", и он спасет вас от хлопот. children.isEmpty - это простая проверка на существование детей, и вам никогда не придется разворачивать или беспокоиться о неожиданном nil.

Кроме того, в качестве примечания, объявление чего-то как необязательного не означает, что оно занимает нулевое пространство - это. .None случай Optional<Array<Person>>.

Ответ 3

Интересно, что в последнее время у нас мало дискуссий по этому же вопросу на работе.

Некоторые полагают, что существуют тонкие смысловые различия. Например, nil означает, что у человека нет детей вообще, но тогда что означает 0? Означает ли это, что "есть дети, целые 0 из них"? Как я уже сказал, чистая семантика "имеет 0 детей" и "не имеет детей" не имеет никакого значения при работе с этой моделью в коде. В таком случае, почему бы не выбрать более простой и менее осторожный подход?? -y?

Некоторые полагают, что сохранение nil может свидетельствовать о том, что, например, при извлечении модели из бэкэнда что-то пошло не так, и мы получили ошибку вместо детей. Но я думаю, что модель не должна пытаться использовать этот тип семантики, а nil не следует использовать как указание на некоторую ошибку в прошлом.

Я лично считаю, что модель должна быть настолько тупой, насколько это возможно, и самым тупым вариантом в этом случае является пустой массив.

У вас есть опция, которая заставит вас перетащить это ? до конца дней и использовать guard let, if let или ?? снова и снова.

Вам NSCoding дополнительная логика разворачивания для реализации NSCoding, вам придется делать person.children?.count?? 0 person.children?.count?? 0 вместо простого person.children.count когда вы показываете эту модель в любом контроллере вида.

Конечной целью всех этих манипуляций является отображение чего-либо в пользовательском интерфейсе. Вы действительно сказали бы

"У этого человека нет детей" и "У этого человека есть 0 детей" для nil и пустого массива соответственно? Надеюсь, вы не будете :)

Последняя капля

Наконец, и это действительно самый сильный аргумент, который я имею

В Cocoa существуют такие примеры: UIViewController :: childViewControllers и многое другое.

Даже из чистого мира Swift: словаря: ключи, хотя это может быть немного надуманным.

Почему это нормально для людей, которые имеют nil детей, но не для SKNode? Для меня аналогия идеальна. Эй, даже SKNode метода SKNode - это children :)

Мое мнение: должно быть очевидной причиной сохранения этих массивов как опций, как действительно хороших, иначе пустой массив предлагает такую же семантику с меньшей разверткой.

Последняя последняя солома

Наконец, некоторые ссылки на очень хорошие статьи, каждая из которых

В сообщении Наташи вы найдете ссылку на сообщение в блоге NSHipster и в параграфе Swiftification вы можете прочитать следующее:

Например, вместо того, чтобы пометить значения NSArray как значения NULL, многие API были изменены, чтобы возвращать пустой массив - семантически они имеют одинаковое значение (т.е. Ничего), но необязательный массив намного проще работать с

Ответ 4

Иногда есть разница между чем-то несуществующим и пустым.

Пусть у нас есть приложение, в котором пользователь может изменить список телефонных номеров, и мы сохраняем указанные изменения как modifiedPhoneNumberList. Если никаких изменений не произошло, массив должен быть равен нулю. Если пользователь изменил проанализированные числа, удалив их, весь массив должен быть пустым.

Пусто означает, что мы собираемся удалить все существующие телефонные номера, ноль означает, что мы сохраним все существующие телефонные номера. Разница здесь имеет значение.

Когда мы не можем различить, является ли свойство пустым или не существующим или не имеет значения пустое, это путь. Если Person потерял своего единственного потомка, мы должны просто удалить этого потомка и иметь пустой массив, а не проверять, равно ли count 1, а затем установить для всего массива значение nil.

Ответ 5

Я всегда использую пустые массивы.

По моему скромному мнению, самая важная цель опций в Swift - безопасно обернуть некоторую ценность, которая может быть равна нулю. Массив уже действует как этот тип обертки - вы можете спросить массив, есть ли у него что-нибудь внутри и получить доступ к его значениям (s) безопасно для циклов, сопоставления и т.д. Нужно ли положить оболочку в оболочку? Я так не думаю.

Ответ 6

Swift предназначен для использования дополнительного значения и дополнительной развертки.

Можно также объявить массив, как ноль, так как это позволит сэкономить вам очень маленький (почти не заметный) объем памяти.

Я бы пошел с необязательным массивом вместо массива, который представляет значение nil, чтобы сохранить Swift Design Patterns счастливым :)

Я также думаю

if let children = children {

}

выглядит лучше, чем:

if(children != nil){

}