来自HTML的NSAttributedString在主线程上的行为就像多线程

时间:2019-05-15 17:34:10

标签: html swift multithreading concurrency nsattributedstring

我正在主线程上将某些HTML转换为NSAttributedString(Apple告诉您的方式)。需要一些时间,然后它继续执行该块的其余部分。

现在,如果另一个块也排队等待在线程中运行(例如,在从HTTP请求获得响应之后),我希望它在之后运行其他所有事情都完成了,但是事实并非如此:它们并行运行,就好像它们在不同的线程上一样。我确实在所有地方都放有断言,以确保它在主线程上。

我做了一个实验“ Single View App”项目来对此进行测试,文件包含一个非常长的html字符串(如<p>lorem</p> ipsum <b>dolor</b> <i><u>sit</u> amet</i>)和一个包含以下代码的视图控制器:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        dispatchStuff()
        for _ in 0..<10 {
            // slowOperation()
            parseHTML()
        }
    }

    func dispatchStuff() {
        for i in 0..<10 {
            let wait = Double(i) * 0.2
            DispatchQueue.main.asyncAfter(deadline: .now() + wait) {
                assert(Thread.isMainThread, "not main thread!")
                print(" dispatched after \(wait) seconds")
            }
        }
    }

    // just loads a big lorem ipsum full of html tags
    let html: String = {
        let filepath = Bundle.main.path(forResource: "test", ofType: "txt")!
        return try! String(contentsOfFile: filepath)
    }()

    var n = 0
    func slowOperation() {
        n += 1
        assert(Thread.isMainThread, "not main thread!")
        print("slowOperation \(n) START")
        var x = [0]
        for i in 0..<10000 {
            x.removeAll()
            for j in 0..<i {
                x.append(j)
            }
        }
        print("slowOperation \(n) END")
        print("")
    }

    var m = 0
    func parseHTML() {
        m += 1
        assert(Thread.isMainThread, "not main thread!")
        print("parseHTML \(m) START")
        let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html]
        let attrString = try! NSAttributedString(data: Data(html.utf8), options: options, documentAttributes: nil)
        print("parseHTML \(m) END")
        print("")
    }
}

如果运行它,则控制台将如下所示:

parseHTML() uncommented

...全部混合在一起,这对我来说是令人惊讶的行为。

但是,如果您在viewDidLoad()中注释了对parseHTML()的呼叫并取消了对slowOperation()的注释,则会得到类似以下的内容:

slowOperation() uncommented

...这就是我所期望的。那么,这是怎么回事?我对线程如何工作的理解是错误的吗?

1 个答案:

答案 0 :(得分:2)

我最初的怀疑是正确的。 NSAttributedString init(data:options:documentAttributes:)的实现调用CFRunLoopRun()。这样做可以使队列(在这种情况下为主队列)上的其他排队的阻塞/关闭运行。

这就是为什么您在主队列上看到似乎是异步输出的原因。

我将您的代码放入一个简单的命令行应用程序中,并在print中的dispatchStuff上设置了一个断点。堆栈跟踪显示,在调用NSAttributedString init的过程中,有对_CGRunLoopRun的内部调用,这导致对dispatchStuff中排队的闭包之一的调用。

enter image description here