Zhou Haibo
Zhou Haibo

Reputation: 2078

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

Upvotes: 1

Views: 113

Answers (2)

Rob
Rob

Reputation: 437842

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: ...)

Upvotes: 2

Jeffery Thomas
Jeffery Thomas

Reputation: 42598

Let me start off by saying, I have no idea, but I have an idea for you to test. Move DispatchQueue.global().async to the first line of 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
    }
  }
}

This isolates the change to just calling loadLevel(). If this works as expected, then keep moving the DispatchQueue.global().async call down until it does break.

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

Upvotes: 0

Related Questions