Как реализовать функции setValue и getValue с поддержкой nil значений
Что проверяют
Понимаете ли вы разницу между «ключа нет» и «ключ есть, значение nil» и как её выражает система типов Swift. Это та же проблема, что null против отсутствующего поля в JSON для PATCH-запроса. Следом спрашивают, что станет со словарём при записи nil через сабскрипт.
Как отвечать
Начните с ловушки: для любого словаря запись dict[key] = nil не сохраняет nil, а удаляет ключ, даже если тип значения опциональный. Поэтому хранилище, которое различает два состояния, строится на двойном опционале:
- хранение:
private var storage: [String: T?], обобщённый тип вместоAny, чтобы не терять типизацию и не ловить неявные обёртки; - запись:
storage.updateValue(value, forKey: key). Метод принимаетValue, то естьT?, и честно сохраняет nil как значение. Запись через сабскрипт с литераломnilснова удалит ключ; - удаление: отдельный метод через
removeValue(forKey:), чтобы намерение было явным; - чтение:
storage[key]возвращаетT??. Разбирается черезswitch:.noneзначит ключа нет,.some(.none)значит сохранён nil,.some(.some(let v))это значение.
Двойной опционал в публичном API читается плохо, поэтому сильный ответ заворачивает его в перечисление вроде enum Lookup<T> { case missing, null, value(T) }. Добавьте потокобезопасность: сделайте хранилище actor или защитите словарь блокировкой, если к нему обращаются из разных потоков.
Хорошо привести примеры из платформы: UserDefaults.set(nil, forKey:) удаляет значение, а NSDictionary не хранит nil вовсе, для этого есть NSNull.
Где спотыкаются
Берут [String: Any?] и возвращают Any?. Двойной опционал неявно схлопывается или упаковывается в Any, при печати появляется Optional(nil) и прочие сюрпризы, а отличить отсутствие от nil уже нельзя.
Называют методы setValue(_:forKey:) в наследнике NSObject. Это сигнатура KVC, компилятор потребует override, а поведение смешается с key-value coding.
Проверяют результат через if let и считают, что nil означает отсутствие ключа. Для T?? if let снимает только внешний уровень, и сохранённый nil проходит как найденное значение.