DispatchQueue 闭包中的字符串插值警告

String interpolation warning in DispatchQueue closure

假设我得到以下代码,它工作正常。

  override func viewDidLoad() {
    super.viewDidLoad()

    // 1. put loadLevel() in background queue
    DispatchQueue.global().async { [weak self] in
      self?.loadLevel()
    }
  }

  func loadLevel() {
    var clueString = ""
    var solutionString = ""
    var letterBits = [String]()

    // 2. some heavy code here

    DispatchQueue.main.async { [weak self] in
      // 3. push some UI code back to main thread
    }

但是,当我将后台队列移动到 loadLevel() 内部并覆盖繁重的代码和 UI 代码时,我遇到一个问题,即在启动应用程序时 UI 更新为空值.那么这两种方式有什么不同呢?

  override func viewDidLoad() {
    super.viewDidLoad()
    
    // 1. call loadLevel
    loadLevel()
  }

  func loadLevel() {
    var clueString = ""
    var solutionString = ""
    var letterBits = [String]()

    DispatchQueue.global().async { [weak self] in
      // 2. some heavy code in background queue

      DispatchQueue.main.async {
        // 3. push UI code back to main thread
      }
    }
  }

用里面的重代码更新第二个代码。

我发现了这个问题,它实际上与 GCD 无关。此问题在行 Bundle.main.url(forResource: "level\(self?.level)" 中,它会产生字符串插值警告。结果资源加载得到 nil 我猜。

因为我在这里使用弱引用[weak self]作为捕获列表,所以我需要在全局变量level之前放置self?,以防在闭包中使用它。如果我给它一个像 \(self?.level ?? 0) 这样的默认值,那么这个问题就解决了。

但是这里是属性处理这个String插值的方式吗?或者这里应该涉及一些更好的方法?

override func viewDidLoad() {
      super.viewDidLoad()
      
      // 1. call loadLevel
      loadLevel()
    }

    func loadLevel() {
      var clueString = ""
      var solutionString = ""
      var letterBits = [String]()

      DispatchQueue.global().async { [weak self] in
        if let levelFileURL = Bundle.main.url(forResource: "level\(self?.level)", withExtension: "txt") {
            if let levelContents = try? String(contentsOf: levelFileURL) {
                var lines = levelContents.components(separatedBy: "\n")
                lines.shuffle()
                self?.correctGuess = 0
                print("AAA")
                
                for (index, line) in lines.enumerated() {
                    let parts = line.components(separatedBy: ": ")
                    let answer = parts[0]
                    let clue = parts[1]
                    
                    clueString += "\(index + 1). \(clue)\n"
                    
                    let solutionWord = answer.replacingOccurrences(of: "|", with: "")
                    solutionString += "\(solutionWord.count) letters\n"
                    self?.solutions.append(solutionWord)
                    
                    let bits = answer.components(separatedBy: "|")
                    letterBits += bits
                    print("ABC")
                }
            }
        }

        DispatchQueue.main.async {
          // 3. push UI code back to main thread
        }
      }
    }

首先让我说,我不知道,但我有一个想法供您测试。将 DispatchQueue.global().async 移动到 loadLevel().

的第一行
func loadLevel() {
  DispatchQueue.global().async { [weak self] in
    var clueString = ""
    var solutionString = ""
    var letterBits = [String]()

    // 2. some heavy code in background queue

    DispatchQueue.main.async {
      // 3. push UI code back to main thread
    }
  }
}

这将更改隔离为仅调用 loadLevel()。如果这按预期工作,则继续向下移动 DispatchQueue.global().async 调用直到它中断。

func loadLevel() {
  var clueString = ""

  DispatchQueue.global().async { [weak self] in
    var solutionString = ""
    var letterBits = [String]()

    // 2. some heavy code in background queue

    DispatchQueue.main.async {
      // 3. push UI code back to main thread
    }
  }
}

您参考了:

let resource = Bundle.main.url(forResource: "level\(self?.level)" withExtension: ...)

警告是

String interpolation produces a debug description for an optional value; did you mean to make this explicit?

编译器警告您正在执行可选值的字符串插值。

让我们考虑一个更简单的示例,以显示当您使用可选值进行字符串插值时会发生什么:

print("\(self?.level)")

如果级别是xxx,它会打印

Optional("xxx")

显然,如果 selflevel 是可选的,它只会说:

nil

显然,这些都不是您想要的。所以,打开可选的。例如

guard let level = self?.level else { return }

let resource = Bundle.main.url(forResource: "level\(level)" withExtension: ...)