Is this code showing that CFAttributedString isn't thread safe? Or am I doing something wrong in the setup?
I thought that CFAttributedString was safe to read from multiple threads, but I see crashes in this code every few runs.
@IBAction func testIt(_ sender: Any?) {
let testString = "Hello world! Lets make this a bit longerrrrrrrrrrrrrrrr." as CFString
let testStringLength = CFStringGetLength(testString)
let testAttributedString = CFAttributedStringCreateMutable(kCFAllocatorDefault, testStringLength)
CFAttributedStringReplaceString(testAttributedString, CFRange(location: 0, length: 0), testString)
CFAttributedStringEndEditing(testAttributedString)
for i in 0..<testStringLength {
let range = CFRange(location: i, length: 1)
let keyAndValue = "\(i)" as CFString
CFAttributedStringSetAttribute(testAttributedString, range, keyAndValue, keyAndValue)
}
let immutableTestAttributedString = CFAttributedStringCreateCopy(kCFAllocatorDefault, testAttributedString)
DispatchQueue.concurrentPerform(iterations: 100) { _ in
var index: CFIndex = 0
var effectiveRange: CFRange = CFRange(location: 0, length: 0)
while index < testStringLength {
// Crash happens here EXC_BAD_ACCESS (code=1, address=0x24)
let _ = CFAttributedStringGetAttributes(immutableTestAttributedString, index, &effectiveRange)
index = effectiveRange.location + effectiveRange.length
}
}
}
let testString = "Hello world! Lets make this a bit longerrrrrrrrrrrrrrrr." as CFString
This is a Swift string masquerading as a CFString
, which is about two layers of indirection and invokes a very different code path down in the guts (whether that should still work or not is between you and a radar).
Try creating a proper CFString
and see if it works the way you expect.
var bytes = Array("Hello world! Lets make this a bit longerrrrrrrrrrrrrrrr.".utf16)
let testString = CFStringCreateWithCharacters(nil, &bytes, bytes.count)
(Of course I highly recommend doing all this work in NSAttributedString
rather than CFAttributedString
. The Swift->Foundation bridging is much simpler and is used constantly compared to the Swift->Foundation->CoreFoundation bridging. This may just be a bug in that bridging, but your world is still going to be a lot easier if you avoid it.)
While I haven't been able to reproduce the problem w/ a pure CFString, this is definitely not thread safe. CoreFoundation is open source (sort-of…, but enough for this purpose), so you can look at the code yourself. Eventually CFAttributedStringGetAttributes
calls blockForLocation
, which updates an internal cache and does not having locking around that. I haven't seen anything in the docs that promises this is thread safe.