Swift: поддержание атомарности в блочном исполнении с использованием слабой самости

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

api.call() { [weak self] (result, error) in
  if (error == nil) {
    setGlobalState()
    self?.doSomething()
  } else {
    setSomeErrorState()
    self?.doSomethingElse()
  }
}

Но мне кажется, что если self равно nil, то теперь состояния несовместимы, потому что setGlobalState() выполнил бы, а self?.DoSomething() этого не сделал.

Казалось бы, разумно сделать следующее:

api.call() { [weak self] (result, error) in
  guard let self = self else { return }

  if (error == nil) {
    setGlobalState()
    self.doSomething()
  } else {
    setSomeErrorState()
    self.doSomethingElse()
  }
}

Является ли мое беспокойство по поводу отсутствия атомности в первом случае законным? Должен ли последний случай быть лучшей практикой, когда речь идет о блоке, который использует слабое "я"?

1 ответ

На этот вопрос нет простого ответа, поскольку правильный ответ зависит от того, что doSomething а также doSomethingElse что-то здесь неясно.

Сказав это, вы говорите:

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

api.call() { [weak self] (result, error) in
  if error == nil {
    setGlobalState()
    self?.doSomething()
  } else {
    setSomeErrorState()
    self?.doSomethingElse()
  }
}

Но мне кажется, что если self равно nil, теперь состояния несовместимы, потому что setGlobalState() казнил бы, но self?.doSomething() не.

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

Обычно, когда вы видите что-то подобное, doSomething или doSomethingElse обновляют пользовательский интерфейс или что-то уникальное для чего-либо selfесть, в котором вышеупомянутый шаблон действительно предпочтительнее. Приведенный выше фрагмент кода гарантирует, чтоsetGlobalState или setSomeErrorState имеет место, но если self является, например, контроллером представления, он гарантирует, что мы не удерживаем его искусственно просто для обновления пользовательского интерфейса, которого больше нет.

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

Итак, вы продолжаете говорить:

Казалось бы, разумнее всего сделать следующее:

api.call() { [weak self] (result, error) in
  guard let self = self else { return }

  if error == nil {
    setGlobalState()
    self.doSomething()
  } else {
    setSomeErrorState()
    self.doSomethingElse()
  }
}

Проблема здесь в том, что если selfосвобождается до вызова обработчика завершения API, то он не будет делать ничего из этого. Вы выполнили вызов API, но ни одинsetGlobalState/doSomething ни setSomeErrorState/doSomethingElseбудет называться. Итак, если вы действительно устанавливаете глобальные состояния и свойства синглтона, вы правы в том, что они будут внутренне согласованными, но теперь они потенциально не синхронизированы с вашим веб-сервисом.

Вы используете этот второй шаблон, только если (а) важно, например, если setGlobalState называется, то doSomething также должен быть вызван; но это (б) еслиselfосвобожден, нормально, что ни один из этих методов не вызван.

Есть третья альтернатива, которую вы можете рассмотреть, а именно опустить [weak self] вместе:

api.call() { result, error in
  if error == nil {
    setGlobalState()
    self.doSomething()
  } else {
    setSomeErrorState()
    self.doSomethingElse()
  }
}

Если важно, чтобы оба setGlobalState а также doSomething (или оба setSomeErrorState а также doSomethingElse) должен вызываться после завершения вызова API, тогда мы просто не будем использовать [weak self]вообще. (Обратите внимание, это работает, только еслиapi был правильно реализован, спроектирован так, чтобы не зависать от закрытия, когда оно было завершено, но все хорошо разработанные асинхронные API все равно это делают.)

Однако следует отметить, что если действительно существует некоторая скрытая глобальная зависимость между setGlobalState а также doSomething (или между setSomeErrorState а также doSomethingElse), что предполагает более глубокую проблему в конструкции. Отдельные предметы должны быть слабо связаны. Сомнительно вообще использовать глобальные переменные или синглтоны с отслеживанием состояния, не говоря уже о том, чтобы возлагать на разработчика приложения ответственность за их синхронизацию.

Итог, выбор из этих трех api варианты завершения полностью зависят от того, что self это что doSomething делает, и что doSomethingElseделает. Я бы не стал категорически отказываться от первой альтернативы, поскольку зачастую это правильное решение.

Другие вопросы по тегам