I'm converting some HTML to an NSAttributedString
on the main thread (the way Apple tells you to). It takes some time and then it continues executing the rest of the block.
Now, if another block is queued to run in the main thread too (e.g. after getting a response from an HTTP request), I would expect it to run after everything else is finished, but that's not what happens: they run in parallel as if they were on different threads. I did put asserts everywhere making sure it's on the main thread.
I made an experiment "Single View App" project to test this, with a file containig a very long html string like <p>lorem</p> ipsum <b>dolor</b> <i><u>sit</u> amet</i>
and a view controller with the following code:
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("")
}
}
if you run it, this is what the console looks like:
...all mixed together, that's the surprising (to me) behavior.
But if in viewDidLoad()
you comment the call to parseHTML()
and uncomment slowOperation()
, you'll get something like this instead:
...which is what I'd expect. So, what's happening here? Is my understanding of how threads work horribly wrong?
My original suspicion was correct. The implementation of NSAttributedString init(data:options:documentAttributes:)
makes calls to CFRunLoopRun()
. Doing so allows other queued up blocks/closures on the queue (main queue in this case) to run.
This is why you are seeing what appears to be asynchronous output on the main queue.
I put your code into a simple command line app and set a breakpoint on the print
in dispatchStuff
. The stack trace shows that during the call to the NSAttributedString init
there is an internal call to _CGRunLoopRun
which results in a call to one of the queued closures from dispatchStuff
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With