UI 异步调用时元素在 Segue 后缺少文本

UI Elements Missing Text After Segue When Called Asynchronously

今天,我在为一所大学构建网络分析应用程序时遇到了一个有趣的难题 class。从异步 DispatchQueue 调用 performSegue 会使目标视图控制器中某些 UI 的文本变为空白。按钮、标签等。就好像它们的文本属性设置为空字符串一样。

我会进一步解释。

我有一个名为 Globals 的自定义 Swift class,它用于存放(显然)需要在应用程序范围内读写的全局变量。

我有一个 UIViewController 作为我的应用程序的加载屏幕。

我有一个 UIViewController 在加载屏幕完成时加载。

我在一个单独的 Swift class 中有一个 URLSession 运行ning,由加载屏幕视图控制器调用。它的目的是下载一个已知大小的小文件,并通过测量下载它所花费的时间来提供速度估计。这是该函数:

func testSpeed() {

    Globals.shared.dlStartTime = Date()

    if Globals.shared.currentSSID == "" {
        Globals.shared.bandwidth = 0
        Globals.shared.DownComplete = true
    } else {

        let url = URL(string: "[EXAMPLE]")
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
        let task = session.downloadTask(with: url!)

        task.resume()
    }
}

public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {

    Globals.shared.dlFileSize = (Double(totalBytesExpectedToWrite) * 8) / 1000

    let progress = (Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) * 100.0

    Globals.shared.dlprogress = Int(progress)
}

public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {

    let elapsed = Double( Date().timeIntervalSince(Globals.shared.dlStartTime))
    Globals.shared.bandwidth = Int(Globals.shared.dlFileSize / elapsed)
    Globals.shared.DownComplete = true
    Globals.shared.dataUse! += (Globals.shared.dlFileSize! / 8000)
}

如您所见,URLSession 使用委托 URLSessionDelegateURLSessionDownloadDelegate 将下载进度传递到加载屏幕,让用户知道下载进度。

您还可以看到,有一个全局布尔变量 DownComplete,它让应用程序的其余部分知道进程何时完成。

返回加载屏幕。有一个名为 progressLabel 的完成标签以及一个名为 MainProgress 的自定义径向进度视图。这是代码:

override func viewDidAppear(_ animated: Bool) {

    [...]

    Networking().testSpeed()
    updateProgress()
}

func updateProgress() {
    DispatchQueue.global().async {
        while Globals.shared.DownComplete == false {
            DispatchQueue.main.sync {
                self.progressLabel.text = String(Globals.shared.dlprogress) + "%"
                self.MainProgress.animate(fromAngle: self.MainProgress.angle, toAngle: Double(Globals.shared.dlprogress)
            }
        }
        self.performSegue(withIdentifier: "LoadCompleteSegue", sender: self)
    }
}

想法是 运行 一个 while 循环,该循环等待进程完成,然后再加载下一个视图控制器。

但是为了不阻塞主线程,while 循环必须 运行 异步。

...但是要正确更新 UI,这些调用必须在同步块中进行。

......但是为了不完全跳过这个过程,performSegue 必须在与 while 循环相同的线程中完成。我认为这就是导致我出现问题的原因。

我在下面得到了下一个视图控制器的屏幕截图,其中我已经勾勒出 应该 是红色框中的文本的区域。因为令我困惑的是它不影响 所有 UI 个元素,只有几个

Screenshot: Missing Elements

编辑:

我知道代码不是最干净的,但这是我的 "testgrounds" Git 分支。请多多包涵。

您必须在主队列上执行 segue。如果您在 while 循环运行后分派它,它应该可以正常工作。尝试将 updateProgress() 函数更新为以下内容:

func updateProgress() {
    DispatchQueue.global().async {
        while Globals.shared.DownComplete == false {
            DispatchQueue.main.sync {
                self.progressLabel.text = String(Globals.shared.dlprogress) + "%"
                self.MainProgress.animate(fromAngle: self.MainProgress.angle, toAngle: Double(Globals.shared.dlprogress)
            }
        }
        DispatchQueue.main.async {
            self.performSegue(withIdentifier: "LoadCompleteSegue", sender: self)
        }
    }
}

你做得很糟糕。你可以只使用 NSNotificatioCenter

在您的 viewDidLoad 中,我会观察到这些事件:

NotificationCenter.default.addObserver(self, selector: #selector(processFinished), name: NSNotification.Name(rawValue: "ProcessFinished"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(ProcessUpdating), name: NSNotification.Name(rawValue: "ProcessUpdating"), object: nil)

在你相应的代表中我会把这个:

// complete event
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ProcessFinished"), object: nil, userInfo: nil)

// progress event
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ProcessUpdating"), object: nil, userInfo: ["progress" : 11])

然后您可以通过以下方式处理您的事件:

    func processFinished(notification: Notification) {
        // perform yout segue
        print("processFinished")

    }

    func ProcessUpdating(notification: Notification) {
        // update UI
        let progress = notification.userInfo!["progress"] as! Int
        print("progress: \(progress)")
    }