Search code examples
iosswiftstring-interpolation

String interpolation warning in DispatchQueue closure


Say I get below code, and it works fine.

  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
    }

However, when I move the background queue to inside loadLevel() and cover the heavy code and UI code, I get an issue that UI is updated with empty values when launching the app. So what is the different of this two ways?

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

Update the 2nd code with the heavy code inside.

I found the issue, it is not related with GCD actually. This issue is in line Bundle.main.url(forResource: "level\(self?.level)", which produces a String interpolation warning. And result resource load get nil I guess.

As I used weak reference [weak self] as capture list here, I need to put self? before the global variable level in case to use it in closure. If I give it a default value like \(self?.level ?? 0), then this issue is fixed.

But is it that the property way to deal with this String interpolation here? Or some better approach should be involved here?

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

Solution

  • You have a reference to:

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

    The warning is

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

    The compiler is warning you that you are performing string interpolation of an optional value.

    Let's consider a simpler example, to show what happens when you do string interpolation with optionals:

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

    If level was xxx, it would print

    Optional("xxx")

    And obviously if self or level were optional, it would just say:

    nil

    Clearly, neither of these are quite what you want. So, unwrap the optional. E.g.

    guard let level = self?.level else { return }
    
    let resource = Bundle.main.url(forResource: "level\(level)" withExtension: ...)