Swift:使用弱自我维护基于块的执行中的原子性

Swift: Maintaining atomicity in a block-based execution using weak self

我经常看到像下面这样使用 weak self 的代码:

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()
  }
}

我对第一种情况缺乏原子性的担忧是否合理?对于使用 weak self 的块,后一种情况应该是最佳实践吗?

这个问题没有简单的答案,因为正确答案取决于 doSomethingdoSomethingElse 在做什么,这里不清楚。

话虽如此,你说:

I often see code that uses weak self like below:

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

But it seems to me that if self is nil, now the states are inconsistent because setGlobalState() would have executed but self?.doSomething() did not.

从技术上讲,这确实是个问题,但这是一种非常不寻常的情况。但如果这是一个问题,那很可能是更深层次问题的代码味道。

通常,当您看到类似这样的内容时,doSomethingdoSomethingElse 正在更新 UI 或 self 所独有的内容,其中上述模式实际上是更可取。上面的代码片段确保 setGlobalStatesetSomeErrorState 发生,但是如果 self 是,例如,视图控制器,它确保我们不会人为地保留它只是为了更新一个UI 不再存在。

但是如果 doSomethingdoSomethingElse 不只是更新 UI,而是像您在后续评论中建议的那样“设置一些单例状态” ,那么你是对的,上面的内容不会达到你想要的。

那么,你接着说:

It would seem like the sensible thing to do is as follows:

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/doSomethingsetSomeErrorState/doSomethingElse 都不会被调用。因此,如果您真的要设置全局状态和单例属性,那么它们将在内部保持一致是对的,但它们现在可能与您的 Web 服务不同步。

只有在以下情况下才使用第二种模式:例如,如果 setGlobalState 被调用,那么 doSomething 必须 是必不可少的称为;但是 (b) 如果 self 被释放,那么 两个 方法都不会被调用。

您可能要考虑第三种选择,即完全省略 [weak self]

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

如果 setGlobalStatedoSomething(或 setSomeErrorStatedoSomethingElse)必须在 API 调用完成后调用,那么我们根本就不会使用 [weak self] 。 (请注意,这仅在 api 被正确实现时才有效,设计为在完成时不会挂在闭包上,但所有设计良好的异步 API 无论如何都会这样做。)

不过,应该注意的是,如果 setGlobalStatedoSomething 之间(或 setSomeErrorStatedoSomethingElse 之间确实存在一些隐藏的全局依赖关系,那么暗示了设计中更深层次的问题。单独的对象应该是松耦合的。使用全局变量或有状态单例都是值得怀疑的,更不用说让应用程序开发人员承担保持两者同步的责任了。

最重要的是,这三个 api 完成选项的选择完全取决于 self 是什么,doSomething 做什么,以及 doSomethingElse 做什么。我不会断然拒绝第一种选择,因为它通常是正确的解决方案。