Предупреждение Xcode: неизменяемое свойство не будет декодировано, поскольку оно объявлено с начальным значением, которое не может быть перезаписано.

Запустив Xcode 12, мой проект Swift 5 Xcode теперь имеет предупреждения всякий раз, когда тип Decodable или Codable объявляет константу let с начальным значением.

struct ExampleItem: Decodable {
    let number: Int = 42 // warning
}

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

Xcode предлагает изменить let на var:

Исправлено: вместо этого сделайте свойство изменяемым

    var number: Int = 42

Он также предлагает исправление:

Исправление: установите начальное значение с помощью инициализатора или явно определите перечисление CodingKeys, включая регистр «заголовок», чтобы отключить это предупреждение.

Какова цель этого нового предупреждения? Должен ли он быть услышан или проигнорирован? Может ли этот тип предупреждения быть заглушен?

Должно ли быть реализовано исправление Xcode? Или есть лучшее решение?


person pkamb    schedule 24.06.2020    source источник


Ответы (3)


Объяснение Ноя верно. Это распространенный источник ошибок, и не сразу становится понятно, что происходит из-за «волшебного» поведения синтеза Codable, поэтому я добавил это предупреждение в компилятор, поскольку оно обращает ваше внимание на тот факт, что свойство не будет декодировано, и заставляет вас явно вызывать его, если это ожидаемое поведение.

Как объясняет исправление, у вас есть несколько вариантов, если вы хотите отключить это предупреждение — какой из них вы выберете, зависит от точного поведения, которое вы хотите:


  1. Передайте начальное значение через init:
struct ExampleItem: Decodable {
  let number: Int
    
  init(number: Int = 42) {
    self.number = number
  }
}

Это позволит декодировать number, но вы также можете передать экземпляры ExampleItem, где используется значение по умолчанию.

Вы также можете использовать его непосредственно внутри init во время декодирования:

struct ExampleItem: Decodable {
  let number: Int
    
  private enum CodingKeys: String, CodingKey {
    case number
  }
    
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    number = try container.decodeIfPresent(Int.self, forKey: .number) ?? 42
  }
}

Это позволит декодировать number, но использовать 42 в качестве значения по умолчанию, если декодирование не удастся.


  1. Сделайте свойство var, хотя вы также можете сделать его private(set) var:
struct ExampleItem: Decodable {
  var number: Int = 42
}

Создание var позволит декодировать number, но также позволит вызывающим абонентам изменять его. Вместо этого, пометив его как private(set) var, вы можете запретить это, если хотите.


  1. Определите явное перечисление CodingKeys:
struct ExampleItem: Decodable {
  let number: Int = 42
  
  private enum CodingKeys: CodingKey {}
}

Это предотвратит декодирование number. Поскольку в перечислении нет регистров, это дает понять компилятору, что нет свойств, которые вы хотите декодировать.

person Suyash Srijan    schedule 24.06.2020
comment
Здорово получить ответ от человека, внедрившего новую ошибку. Могу я спросить, как вы нашли этот вопрос так быстро и с новой учетной записью SO?! - person pkamb; 24.06.2020
comment
@pkamb Я видел ссылку на этот вопрос в Твиттере. У меня еще не было учетной записи S ​​/ O, поэтому я создал ее, а затем оставил ответ. - person Suyash Srijan; 25.06.2020
comment
Каков самый простой способ отключить предупреждение в следующем случае? (идентификатор отсутствует в файле json.) struct JsonCard: Codable { let id = UUID() let a: String let b: String } - person Hugo F; 19.09.2020
comment
@HugoF Вы можете явно объявить перечисление CodingKeys и исключить свойство id. - person Suyash Srijan; 19.09.2020
comment
Принимаются ли какие-либо меры для отключения этого предупреждения? Есть случаи, когда это желательно, но теперь мы видим предупреждения для действительного кода. Мы понимаем, как работает Codable, и хотели бы иметь возможность использовать правильный быстрый код без предупреждения. (Я могу провести параллели с discardableResult, который позволяет вызывающей стороне игнорировать результат без генерации предупреждения). Написание собственных ключей кодирования намного более подвержено ошибкам, чем использование кода, сгенерированного компилятором. (В общем, чем меньше строк кода, тем лучше) - person Sam; 17.10.2020
comment
@Sam Есть несколько способов отключить это предупреждение, как описано выше, и явное написание CodingKeys — лишь один из них. Swift не предлагает общего способа отключить конкретное предупреждение. - person Suyash Srijan; 18.10.2020
comment
Я вижу это, однако желаемое поведение заключается в том, что значение кодируется в json, но не декодируется. То, как работает Codable, имеет большой смысл, если вы думаете о том, как будет выглядеть автоматически сгенерированный код. Но что меня раздражает, так это то, что у нас не остается возможности для сгенерированного компилятором кода следовать довольно распространенному соглашению для гетерогенных массивов json. (Надеюсь, я не слишком развязно звучу ????) - person Sam; 18.10.2020
comment
tbh написание CodingKeys не так уж удовлетворительно. Обычно для того, чтобы отключить предупреждение, нужно сделать что-то простое, например добавить as Any или добавить скобки вокруг чего-либо. Запись CodingKeys более сложна и означает, что о поле, добавленном в будущем, можно забыть. Если бы у Swift была надлежащая система подавления предупреждений, все было бы в порядке. Теперь мне нужно написать CodingKeys примерно для 70 структур... - person Jonathan.; 28.10.2020
comment
наш вариант использования заключается в том, что у нас есть структуры, которые можно кодировать, при кодировании значения всегда должны быть определенной строкой, при декодировании это не имеет значения, значение все равно должно быть одинаковым, поэтому на самом деле мы не можем опустить их из CodingKeys потому что тогда они не будут закодированы. Я действительно не думаю, что предупреждение должно быть здесь, когда оно просто информативно для разработчика, и нет реального способа подавить его. - person Jonathan.; 28.10.2020
comment
Я согласен. У меня есть вариант использования, когда я не хочу, чтобы это предупреждение было там. let id = UUID() Структура содержит намного больше переменных. Кажется странным, что я должен явно создавать init/или перечисления только для того, чтобы заставить замолчать что-то, что мне нужно... Может быть, справиться с этим по-другому? - person Just a coder; 01.12.2020
comment
О, это было явно неправильное решение. Почему у нас должно быть предупреждение для полностью корректного и рабочего кода? - person Sulthan; 22.12.2020
comment
Поэтому я реализовал одно из исправлений, преобразовав let в var. Все скомпилировано нормально, но позже обнаружилась проблема, связанная с объектами Realm (vars и collections), которая фактически нарушала функциональность приложения. Так что да, внесите исправления, но будьте очень осторожны, так как они могут иметь побочные эффекты. - person wuf810; 09.02.2021
comment
Просто добавляю и сюда. Это очень глупое предупреждение, особенно сейчас, когда нам нужно, чтобы наши кодируемые объекты также соответствовали Идентифицируемым. Возможно, для этого необходимо создать оболочку свойства. Хотя LET должно быть более чем достаточно.... - person Beau Nouvelle; 26.05.2021

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

Например, рассмотрим этот код:

struct Model: Decodable {
    let value: String = "1"
}

let json = """
{"value": "2"}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model)

На самом деле это напечатает Model(value: "1"), даже если в json, который мы ему дали, было value как "2".

На самом деле вам даже не нужно указывать значение в данных, которые вы декодируете, так как оно все равно имеет начальное значение!

let json = """
{}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model) // prints "Model(value: "1")"

Изменение значения на var означает, что оно будет правильно декодировано:

struct VarModel: Decodable {
    var value: String = "1"
}
let json = """
{"value": "2"}
"""
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
print(varModel) // "VarModel(value: "2")"

Если вы видите эту ошибку, это означает, что ваш код никогда не анализировал правильно рассматриваемое свойство при декодировании. Если вы измените его на var, свойство будет проанализировано правильно, что может быть именно тем, что вам нужно, однако вы должны убедиться, что данные, которые вы декодируете, всегда имеют этот набор ключей. Например, это вызовет исключение (и сбой, поскольку мы используем try!):

let json = """
{}
"""
let decoder = JSONDecoder()

struct VarModel: Decodable {
    var value: String = "1"
}

let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)

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

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

person Noah Gilmore    schedule 24.06.2020

Решение: определите явное перечисление CodingKeys, чтобы предотвратить декодирование id. Например,

struct Course: Identifiable, Decodable {
  let id = UUID()
  let name: String

  private enum CodingKeys: String, CodingKey {
    case name
  }
  
  init(name: String) { self.name = name }
  init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let name = try container.decodeIfPresent(String.self, forKey: .name)
    self.name = name ?? "default-name"
  }
}
person Harry Zhang    schedule 23.11.2020