Crash while archiving data by NSKeyedArchiver - ios

My app uses the following function to serialise a set of custom objects (only relevant code is shown):
class func serializeShoppingLocations(buyLocations: Set<ShoppingLocation>) -> Data {
#if os(iOS)
NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListIOS.ShoppingLocation.self)
#elseif os(watchOS)
NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListWatch.ShoppingLocation.self)
#endif
let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)
return data
}
The conditional compilation for the iOS and watchOS targets is required in order to allow both targets to dearchive the serialised data later, even if they have been archived by the other target.
ShoppingLocation adopts the NSCoding protocol, and inherits from NSObject.
This code crashes at the let data = … instruction with EXC_BAD_ACCESS (code=1, address=0x0).
I know that the error probably indicates that a non-existent object is referred. However, the variable buyLocations is existent, and I can print the value in the debugger, including all values of the objects in the set.
The stack trace is:
The stack trace indicates that the archiver starts to archive the NSSet, and tries to archive one of the elements of the set, but setObjectForKey in the internal mutable dictionary fails.
What could be the reason?
EDIT:
During further testing I realised that the crash happens always when at least 2 threads of the app are active.
Particularly, I found a situation where one thread crashed (red in Xcode) at an instruction different from the one mentioned above:
let dataBlob = NSKeyedArchiver.archivedData(withRootObject: shoppingItem.buyLocations)
and the other thread stopped at the instruction mentioned above (green in Xcode):
let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)
Might it be that NSKeyedArchiver is not thread-safe?

My problem is indeed, as suspected, caused by unsynchronised accesses by multiple threads:
An NSArchiver walks through an object graph, starting at the root object, and archives on the way every object it encounters.
In order to create a correct archive, it is therefore required that the object graph is not mutated until the archiver terminates.
In a single-threaded application, this is guaranteed automatically.
In a multi-threaded application where more than 1 thread can mutate the object graph, the programmer is responsible to ensure this, because the archiver has no way to prevent other threads to mutate the root object. (If, e.g., the archiver would first make a deep copy of the object graph, and then archive the copy, the object graph could be mutated during the deep copy).
It is therefore required to serialise all get and set accesses to the object graph, e.g. by execute them in a serial access queue.

Related

Understanding Swift thread safety

I have encountered a data race in my app using Xcode's Thread Sanitizer and I have a question on how to address it.
I have a var defined as:
var myDict = [Double : [Date:[String:Any]]]()
I have a thread setup where I call a setup() function:
let queue = DispatchQueue(label: "my-queue", qos: .utility)
queue.async {
self.setup {
}
}
My setup() function essentially loops through tons of data and populates myDict. This can take a while, which is why we need to do it asynchronously.
On the main thread, my UI accesses myDict to display its data. In a cellForRow: method:
if !myDict.keys.contains(someObject) {
//Do something
}
And that is where I get my data race alert and the subsequent crash.
Exception NSException * "-[_NSCoreDataTaggedObjectID objectForKey:]:
unrecognized selector sent to instance
0x8000000000000000" 0x0000000283df6a60
Please kindly help me understand how to access a variable in a thread safe manner in Swift. I feel like I'm possibly half way there with setting, but I'm confused on how to approach getting on the main thread.
One way to access it asynchronously:
typealias Dict = [Double : [Date:[String:Any]]]
var myDict = Dict()
func getMyDict(f: #escaping (Dict) -> ()) {
queue.async {
DispatchQueue.main.async {
f(myDict)
}
}
}
getMyDict { dict in
assert(Thread.isMainThread)
}
Making the assumption, that queue possibly schedules long lasting closures.
How it works?
You can only access myDict from within queue. In the above function, myDict will be accessed on this queue, and a copy of it gets imported to the main queue. While you are showing the copy of myDict in a UI, you can simultaneously mutate the original myDict. "Copy on write" semantics on Dictionary ensures that copies are cheap.
You can call getMyDict from any thread and it will always call the closure on the main thread (in this implementation).
Caveat:
getMyDict is an async function. Which shouldn't be a caveat at all nowadays, but I just want to emphasise this ;)
Alternatives:
Swift Combine. Make myDict a published Value from some Publisher which implements your logic.
later, you may also consider to use async & await when it is available.
Preface: This will be a pretty long non-answer. I don't actually know what's wrong with your code, but I can share the things I do know that can help you troubleshoot it, and learn some interesting things along the way.
Understanding the error
Exception NSException * "-[_NSCoreDataTaggedObjectID objectForKey:]: unrecognized selector sent to instance 0x8000000000000000"
An Objective C exception was thrown (and not caught).
The exception happened when attempting to invoke -[_NSCoreDataTaggedObjectID objectForKey:]. This is a conventional way to refer to an Objective C method in writing. In this case, it's:
An instance method (hence the -, rather than a + that would be used for class methods)
On the class _NSCoreDataTaggedObjectID (more on this later)
On the method named objectForKey:
The object receiving this method invocation is the one with address 0x8000000000000000.
This is a pretty weird address. Something is up.
Another hint is the strange class name of _NSCoreDataTaggedObjectID. There's a few observations we can make about it:
The prefixed _NS suggests that it's an internal implementation detail of CoreData.
We google the name to find class dumps of the CoreData framework, which show us that:
_NSCoreDataTaggedObjectID subclasses _NSScalarObjectID
Which subclasses _NSCoreManagedObjectID
Which subclasses NSManagedObjectID
NSManagedObjectID is a public API, which has its own first-party documentation.
It has the word "tagged" in its name, which has a special meaning in the Objective C world.
Some back story
Objective C used message passing as its sole mechanism for method dispatch (unlike Swift which usually prefers static and v-table dispatch, depending on the context). Every method call you wrote was essentially syntactic sugar overtop of objc_msgSend (and its variants), passing to it the receiver object, the selector (the "name" of the method being invoked) and the arguments. This was a special function that would do the job of checking the class of the receiver object, and looking through that classes' hierarchy until it found a method implementation for the desired selector.
This was great, because it allows you to do a lot of cool runtime dynamic behaviour. For example, menu bar items on a macOS app would just define the method name they invoke. Clicking on them would "send that message" to the responder chain, which would invoke that method on the first object that had an implementation for it (the lingo is "the first object that answers to that message").
This works really well, but has several trade-offs. One of them was that everything had to be an object. And by object, we mean a heap-allocated memory region, whose first several words of memory stored meta-data for the object. This meta-data would contain a pointer to the class of the object, which was necessary for doing the method-loopup process in objc_msgSend as I just described.
The issue is, that for small objects, (particularly NSNumber values, small strings, empty arrays, etc.) the overhead of these several words of object meta-data might be several times bigger than the actual object data you're interested in. E.g. even though NSNumber(value: true /* or false */) stores a single bit of "useful" data, on 64 bit systems there would be 128 bits of object overhead. Add to that all the malloc/free and retain/release overhead associated with dealing with large numbers of tiny object, and you got a real performance issue.
"Tagged pointers" were a solution to this problem. The idea is that for small enough values of particular privileged classes, we won't allocate heap memory for their objects. Instead, we'll store their objects' data directly in their pointer representation. Of course, we would need a way to know if a given pointer is a real pointer (that points to a real heap-allocated object), or a "fake pointer" that encodes data inline.
The key realization that malloc only ever returns memory aligned to 16-byte boundaries. This means that 4 bits of every memory address were always 0 (if they weren't, then it wouldn't have been 16-byte aligned). These "unused" 4 bits could be employed to discriminate real pointers from tagged pointers. Exactly which bits are used and how differs between process architectures and runtime versions, but the general idea is the same.
If a pointer value had 0000 for those 4 bits then the system would know it's a real object pointer that points to a real heap-allocated object. All other possible values of those 4-bit values could be used to signal what kind of data is stored in the remaining bits. The Objective C runtime is actually opensource, so you can actually see the tagged pointer classes and their tags:
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_NSMethodSignature = 20,
OBJC_TAG_UTTypeRecord = 21,
// When using the split tagged pointer representation
// (OBJC_SPLIT_TAGGED_POINTERS), this is the first tag where
// the tag and payload are unobfuscated. All tags from here to
// OBJC_TAG_Last52BitPayload are unobfuscated. The shared cache
// builder is able to construct these as long as the low bit is
// not set (i.e. even-numbered tags).
OBJC_TAG_FirstUnobfuscatedSplitTag = 136, // 128 + 8, first ext tag with high bit set
OBJC_TAG_Constant_CFString = 136,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
You can see, strings, index paths, dates, and other similar "small and numerous" classes all have reserved pointer tag values. For each of these "normal classes" (NSString, NSDate, NSNumber, etc.), there's a special internal subclass which implements all the same public API, but using a tagged pointer instead of a regular object.
As you can see, there's a value for OBJC_TAG_NSManagedObjectID. It turns out, that NSManagedObjectID objects were numerous and small enough that they would benefit greatly for this tagged-pointer representation. After all, the value of NSManagedObjectID might be a single integer, much like NSNumber, which would be wasteful to heap-allocate.
If you'd like to learn more about tagged pointers, I'd recommend Mike Ash's writings, such as https://www.mikeash.com/pyblog/friday-qa-2012-07-27-lets-build-tagged-pointers.html
There was also a recent WWDC talk on the subject: WWDC 2020 - Advancements in the Objective-C runtime
The strange address
So in the previous section we found out that _NSCoreDataTaggedObjectID is the tagged-pointer subclass of NSManagedObjectID. Now we can notice something else that's strange, the pointer value we saw had a lot of zeros: 0x8000000000000000. So what we're dealing with is probably some kind of uninitialized-state of an object.
Conclusion
The call stack can shed further light on where this happens exactly, but what we know is that somewhere in your program, the objectForKey: method is being invoked on an uninitialized value of NSManagedObjectID.
You're probably accessing a value too-early, before it's properly initialized.
To work around this you can take one of several approaches:
A future ideal world, use would just use the structured concurrency of Swift 5.5 (once that's available on enough devices) and async/await to push the work on the background and await the result.
Use a completion handler to invoke your value-consuming code only after the value is ready. This is most immediately-easy, but will blow up your code base with completion handler boilerplate and bugs.
Use a concurrency abstraction library, like Combine, RxSwift, or PromiseKit. This will be a bit more work to set up, but usually leads to much clearer/safer code than throwing completion handlers in everywhere.
The basic pattern to achieve thread safety is to never mutate/access the same property from multiple threads at the same time. The simplest solution is to just never let any background queue interact with your property directly. So, create a local variable that the background queue will use, and then dispatch the updating of the property to the main queue.
Personally, I wouldn't have setup interact with myDict at all, but rather return the result via the completion handler, e.g.
// properties
var myDict = ...
private let queue = DispatchQueue(label: "my-queue", qos: .utility) // some background queue on which we'll recalculate what will eventually be used to update `myProperty`
// method doesn't reference `myDict` at all, but uses local var only
func setup(completion: #escaping (Foo) -> Void) {
queue.async {
var results = ... // some local variable that we'll use as we're building up our results
// do time-consuming population of `results` here;
// do not touch `myDict` here, though;
// when all done, dispatch update of `myDict` back to the main queue
DispatchQueue.main.async { // dispatch update of property back to the main queue
completion(results)
}
}
}
Then the routine that calls setup can update the property (and trigger necessary UI update, too).
setup { results in
self.myDict = results
// also trigger UI update here, too
}
(Note, your closure parameter type (Foo in my example) would be whatever type myDict is. Maybe a typealias as advised elsewhere, or better, use custom types rather than dictionary within dictionary within dictionary. Use whatever type you’d prefer.)
By the way, your question’s title and preamble talks about TSAN and thread safety, but you then share a “unrecognized selector” exception, which is a completely different issue. So, you may well have two completely separate issues going on. A TSAN data race error would have produced a very different message. (Something like the error I show here.) Now, if setup is mutating myDict from a background thread, that undoubtedly will lead to thread-safety problems, but your reported exception suggests there might also be some other problem, too...

AddressSanitizer: heap-use-after-free only on Release archive

I have created an example of my class: https://github.com/ChoadPet/H.264-Decoding
When I build my application with DEBUG configuration everything is working fine, but when I archive RELEASE it's crashing on this line:
let status = VTDecompressionSessionDecodeFrame(session,
sampleBuffer: sampleBuffer,
flags: defaultDecodeFlags,
frameRefcon: nil,
infoFlagsOut: nil)
With Address Sanitizer enable I got this error:
Thread 9: Use of deallocated memory
SUMMARY: AddressSanitizer: heap-use-after-free
(.../Frameworks/libclang_rt.asan_ios_dynamic.dylib:arm64+0x1a1f4) in wrap_memmove
...
(if you need more crash info, let me know)
Without: Thread 12: EXC_BAD_ACCESS (code=1, address=0x107dd0020)
I do understand that there is some memory that was freed and it accesses by VTDecompressionSessionDecodeFrame method, but I can't find any address with hex, and I don't understand how this is working perfectly with the DEBUG build.
Before this method, session and sampleBuffer are successfully created(initialized).
Is there are some project settings I can change to DEBUG configuration which can cause the crash? Or somebody can point me out on a code issue?
Thank you!
Changing Optimization Level for Release archive to the same as for Debug - No Optimization[-Onone] hide the problem, but changing build configuration is not the right way to resolve this kinda problem. Also, the problem was not exactly in sampleBuffer. The problem was in blockBufferOut parameter, which goes into sampleBuffer later. I will update the repo source code, so the community can see changes clearly.
So I had before this logic:
// 1. Creating blockBuffer from `bufferPointer`
localFrame.withUnsafeMutableBufferPointer { bufferPointer in
// I should write everything in this body,
// since bufferPointer would be released after the closure
// and so, it will be released from bufferBlock
}
// 2. I called this method, which is creating `sampleBuffer`
CMSampleBufferCreateReady
// 3. I called this method, which is decode frame with session and sampleBuffer
VTDecompressionSessionDecodeFrame
/*
and on this line, it crashes with EXC_BAD_ACCESS,
because the sample wasn't valid anymore, because
on step 1, bufferPointer only valid inside body closure,
so it's release after block when I created sampleBuffer
and later decode it.
This is even pointed by documentation:
Parameters
body
Closure with an UnsafeMutableBufferPointer parameter that points to the
contiguous storage for the array. If no such storage exists, it is created. If
the body has a return value, that value is also used as the return value for the
withUnsafeMutableBufferPointer(_:) method. The pointer argument is valid only for
the duration of the method’s execution.
*/
Summary: if you operate on bufferPointer, do all operations inside a closure.
Added to the answer provided by #vpoltave, I found using a dispatch queue after the samplebuffer creation used to give me an EXC_BAD_ACCESS error or if you turn on address sanitiser, then it would error out in the com.apple.coremedia.videomediaconverter with Use of deallocated memory. Suggestion is to enqueue the raw data instead of the created buffers. For me the source was a frame in elementary format coming in callbacks.

Swift - Is setting variable property concurrency or multithreading safe?

As I know setting a variable property when using concurrency or multithreading is not safe but I can't produce a crash with below code.
class Node {
var data = 0
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = i // Should get crash at this line
}
}
UPDATE1
Thanks #MartinR for pointing out in his comment.
Enable the “Thread Sanitizer” and it'll report an error immediately.
UPDATE2
The code got EXC_BAD_ACCESS KERN_INVALID_ADDRESS crash if changing data to reference type. It doesn't always happen but sometimes it will. For example:
class Data {}
class Node {
var data = Data() // Use reference type instead of value type
}
var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for i in 0...1000 {
concurrentQueue.async {
node.data = Data() // EXC_BAD_ACCESS KERN_INVALID_ADDRESS
}
}
This behavior also happens in Objective-C. Setting object property concurrently will cause crash. But with primitive type, the crash will not happen.
Questions
Does setting value type property concurrently will produce a crash?
If it doesn't produce a crash, what is the difference between setting value type property and setting reference type property?
It's perfect if anyone can also explain why setting reference type property concurrently will produce a crash.
First off
Class value are stored in heap memory while struct/enum value are stored in stack memory and compiler will try to allocate those memory at compile time (according to my first ref and many online answer). You can check using this code:
class MemTest {}
class Node {
var data = MemTest()
}
let node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)
for index in 0...100000 {
concurrentQueue.async {
node.data = MemTest()
withUnsafePointer(to: &node.data) {
print("Node data # \($0)")
}
withUnsafePointer(to: node.data) {
print("Node data value no. \(index) # \($0)")
}
}
How to: run 2 time and check memory address for value changed time 500, switch MemTest between class and struct will show the difference. Struct will show the same while class will show different address between each time.
So changing value type is just like changing address but the the memory blocks will not be restored while changing reference type is not just changing address but also restore and allocate new memory blocks which will cause the program to break.
Secondly
But if running first code block of #trungduc with withUnsafePointer, it will show us that the index var of for loop is allocated on the go and in the heap memory, so why is that?
As mentioned before, compiler will only try to allocate the mem if the value type is calculable at compile time. If the they are not calculable, the value will be allocated in heap memory and stay there till the end of scope (according to my second ref). So may be the explanation here is the system will restored the allocated stack after everything is done - end of scope (This I'm not very sure). So in this case the code will not produce a crash as we know.
My conclusion, the reference type variable's mem will be allocated and restored with no restriction in this case whereas value type variable's mem will be allocated and removed only after the system enter and exit a the scope which contains said variable
Thanks
#Gokhan Topcu for Check section "Memory Management"
#Bruno Rocha for Check section "Heap Allocated Value Types"
Some words
My answer is not very solid and might have lots of grammar and spelling error. All update are appreciated. Thanks in advance
Update
For the part I'm not very sure:
variable index was copied to a new memory address with operator = so it doesn't matter where the scope end, stack will be released after for loop
After some digging, in #trungduc's code, with reference type variable, it will do 3 things:
Allocate new memory for class Data
Reduce reference to old Data stored in node.data, even free old Data if it's no longer referenced
Point node.data to new Data
While for value type it will do 1 thing only:
Point node.data to Integer in stack memory
The major difference is in step 2 where there is a chance the old Data memory is restored
There are possibilities where this scenario will happen with reference type
________Task 1________|________Task 2________
Allocate new Data #1 |
|Allocate new Data #2
Load pointer to old |
Data |
Reduce reference count|
to old Data |
|Load pointer to old
|Data
Free old Data |
|Reduce reference count
|to old Data (!)
|Free old Data (!)
Reference new Data #1 |
|Reference new Data #2
while with value type, this will happen
________Task 1________|________Task 2________
Reference to Integer 1|
|Reference to Integer 2
In the first case we will have various alternative scenarios but in most case, we get a segmentation fault because thread 2 tries to dereference that pointer after thread 1 free it. There might be other issues like memory leaking as we notice, thread 2 might not reduce reference count to Data #1 correctly
Whereas in second case, it's just changing the pointer.
Note
In second case, it will never cause crash on Intel CPU but not guaranteed on other CPUs as well because many CPUs do not promise that doing this will not cause a crash.
Non-atomic doesn't mean that app will crash if multiple threads are using the shared resource.
Atomic Properties
Defining a property as atomic will guarantee that a valid value will be returned. Notice that valid does not always mean correct.
This also does not mean that atomic properties are thread safe. Different threads can attempt to write and read a the same time. One of two values will be returned — the value before the change or the value of the change
Non-Atomic Properties
Non atomic properties has no guarantee regarding the returned value. It can be the correct value, a partially written value or even some garbage value.
It simply means that the final value will not be consistent. You won't know which thread will update the value last.
You can refer the link for more clarification on this.

Having to call fetch twice from CoreData

Both on simulator and my real device, an array of strings is saved upon app termination. When I restart the app and fetchRequest for my persisted data (either from a viewDidLoad or a manual button action), I get an empty array on the first try. It isn't until the second time I fetchRequest that I finally get my data.
The funny thing is that there doesn't seem to be a time discrepancy involved in this issue. I tried setting various timeouts before trying to fetch the second time. It doesn't matter whether I wait 10 seconds to a minute -- or even immediately after the first fetch; the data is only fetched on the second try.
I'm having to use this code to fetch my data:
var results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
while (results.isEmpty) {
results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
}
return results
For my sanity's sake, here's a checklist:
I'm initializing the Core Data Stack using boilerplate code from Apple: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
I'm putting my single DataController instance in a static variable at the top of my class private static let context: NSManagedObjectContext = DataController().managedObjectContext
I'm successfully saving my context and can retrieve the items without any issue in a single session; but upon trying to fetch on the first try in a subsequent session, I get back an empty array (and there lies the issue).
Note** I forgot to mention that I'm building a framework. I am using CoreData with the framework's bundle identifier and using the model contained in the framework, so I want to avoid having to use logic outside of the framework (other than initalizing the framework in the appDelegate).
The Core Data stack should be initialized in applicationDidFinishLaunchingWithOptions located in appDelegate.swift because the psc is added after you're trying to fetch your data.
That boilerplate code from Apple includes:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
/* ... */
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
The saved data isn't available until the addPersistentStoreWithType call finishes, and that's happening asynchronously on a different queue. It'll finish at some point but your code above is executing before that happens. What you're seeing isn't surprising-- you're basically looping until the async call finishes.
You need to somehow delay your fetch until the persistent store has been loaded. There are a couple of possibilities:
Do something sort of like what you're already doing. I'd prefer to look at the persistent store coordinator's persistentStores property to see if any stores have been loaded rather than repeatedly trying to fetch.
Post a notification after the persistent store is loaded, and do your fetch when the notification happens.

Can Core Foundation be used in PLCrashReporter signal callback?

I'm using PLCrashReporter in my iOS project and I'm curious, is it possible to use Core Foundation code in my custom crash callback. The thing, that handle my needs is CFPreferences.Here is part of code, that I create:
void LMCrashCallback(siginfo_t* info, ucontext_t* uap, void* context) {
CFStringRef networkStatusOnCrash;
networkStatusOnCrash = (CFStringRef)CFPreferencesCopyAppValue(networkStatusKey, kCFPreferencesCurrentApplication);
CFStringRef additionalInfo = CFStringCreateWithFormat(
NULL, NULL, CFSTR( "Additional Crash Properties:[Internet: %#]", networkStatusOnCrash);
CFPreferencesSetAppValue(additionalInfoKey, additionalInfo,
kCFPreferencesCurrentApplication);
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}
My target is to collect some system information, just in time when app crashed, e.g Internet connection type.
I know it is not good idea to create own crash callback due to async-safe functions, but this can help.
Also as other option: Is there a way to extend somehow PLCrashReportSystemInfo class?
This is very dangerous. In particular the call to CFStringCreateWithFormat allocates memory. Allocating memory in the middle of a crash handler can lead to battery-draining deadlock (yep; had that bug…) For example, if you were in the middle of free() (which is not an uncommon place to crash), you may already be holding a spinlock on the heap. When you call malloc to get some memory, you may spinlock the heap again and deadlock in a tight-loop. The heap needs to be locked so often and for such short periods of time that it doesn't use a blocking lock. It does the equivalent of while (locked) {}.
You seem to just be reading a preference and copying it to another preference. There's no reason to do that inside a crash handler. Just check hasPendingCrashReport during startup (which I assume you're doing already), and read the key then. It's not clear what networkStatusKey is, but it should still be there when you start up again.
If for any reason it's modified very early (before you call hasPendingCrashReport), you can grab it in main() before launching the app. Or you can grab it in a +load method, which is called even earlier.

Resources