3

I am using https://github.com/williamFalcon/SwiftTryCatch as a workaround for a rare NSInternalInconsistencyException incident.

Here's the code snippet.

private func safePerformBatchUpdates(_ updates: (() -> Void)?, completion: ((Bool) -> Void)? = nil) {

    SwiftTryCatch.try({
        collectionView.performBatchUpdates(updates, completion: completion)
    }, catch: { (error) in
        print("\(error)")
        
        Crashlytics.crashlytics().record(error: error)
        
        recoverFromPerformBatchUpdatesError()
    }, finally: nil)
}

In https://github.com/williamFalcon/SwiftTryCatch, it is mentioning

It was pointed out that without -fobjc-arc-exceptions flag this will lead to memory leaks http://clang.llvm.org/docs/AutomaticReferenceCounting.html#exceptions Therefore, ARC-generated code leaks by default on exceptions, which is just fine if the process is going to be immediately terminated anyway. Programs which do care about recovering from exceptions should enable the option.

How can I add -fobjc-arc-exceptions flag correctly, into my Xcode?

These are the steps I am trying to do

  1. Select the project at the top left of the project window.
  2. Select the target.
  3. Open the build phases pane.
  4. Select "Compile Sources"

Now, there are around 500+ source code files. I was wondering, should I

  1. Only add -fobjc-arc-exceptions flags, to files SwiftTryCatch.h and SwiftTryCatch.m?
  2. Only add -fobjc-arc-exceptions flags, to files SwiftTryCatch.h, SwiftTryCatch.m and any *.swift files which is using SwiftTryCatch?
  3. Add -fobjc-arc-exceptions flags to all 500+ files?
Cœur
  • 37,241
  • 25
  • 195
  • 267
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

1 Answers1

1

You need to compile the file that raises the exception with -fobjc-arc-exceptions. So you would need to recompile UIKit (or probably CoreData), which you cannot do.

Also note that -fobjc-arc-exceptions just helps prevent certain kinds of memory leaks. It does not make the call exception-safe. Exceptions can still leave the system in an undefined state, depending on how the code is writen. Sometimes this undefined state doesn't cause any actual problems, but in general it is not possible to recover from an exception.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks. Then, it seems like it is not possible to avoid memory leakage, when rare `NSInternalInconsistencyException`. But, maybe it still still better than crashing the app. – Cheok Yan Cheng Jan 10 '23 at 16:25
  • Often NSInternalInconsistencyException will log some indication of what the problem was. I often find that there are better ways to work around Core Data flaws than just catching exceptions (as an example of weird Core Data work arounds: https://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different/3927811). Slightly more often, though, it's pointing to an actual bug in your code; particularly accessing something outside of its proper context. (But I'm not saying that Core Data doesn't also have some weird crashing bugs of its own.) – Rob Napier Jan 10 '23 at 16:59
  • In `performBatchUpdates`, I would tend to suspect your code, most likely that your updates aren't consistent. For example, there can be multiple operations at the same indexPath, or a reload of an indexPath you've already deleted or moved. Those are pretty common issues I've had to write code to deal with in the past. Usually the exception will tell you what the mistake was. – Rob Napier Jan 10 '23 at 17:04
  • Here's my code skeleton ( https://gist.github.com/yccheok/f69657ee02f9430dd5de2f420e453363 ) , to my best knowledge to avoid `NSInternalInconsistencyException`. So far, it doesn't produce any crash during development. Just that, I am not sure how crash on production occurs. I think I will re-look into the crash log and rethink again. Btw, do you spot any obvious mistake in my code skeleton? Thanks. – Cheok Yan Cheng Jan 10 '23 at 17:26
  • Oh, yeah, this is a pretty classic example of the kind of code that will mess up corner cases. This is going to get messed up I expect when list changes (addition/deletion/move) interact with element changes (update), and you try to reload a cell that has moved. This is really hard to get right. But your "append all the changes together and then execute them all" is exactly the mistake everyone makes. The last time I built one of these, we used a modified version of https://github.com/lxcid/ListDiff/blob/master/Sources/ListDiff.swift as a starting point ... – Rob Napier Jan 10 '23 at 20:17
  • We then carefully applied the changes as delete, then insert, then moving things that hadn't been deleted, then reloading things that hadn't been moved or deleted. (And of course you must absolutely make sure that your final result matches your model. This is a classic source of bugs.) Search https://stackoverflow.com/search?q=NSInternalInconsistencyException+performBatchUpdates for lots of examples. I wish I had handy code that absolutely does this 100% for you. It's something my teams have had to build a few times. But the exception is a bug in your code, not UIKit. – Rob Napier Jan 10 '23 at 20:20
  • Yes. I do aware update operation doesn’t play well with rest other operations. WWDC does have a video and their recommendation is 2 separate batch update call. (I am currently using such way) They do not have recommendations like “convert move+update to delete+insert” as in ListDiff.swift. I try once but I do not like such outcome, as we will see no more move animation. – Cheok Yan Cheng Jan 11 '23 at 01:09
  • 1
    Perhaps, I can give a try on https://developer.apple.com/forums/thread/692357 , as it should contain logic as shown in ur shared ListDiff.swift, to see whether it meets my requirement. – Cheok Yan Cheng Jan 11 '23 at 01:15
  • `So you would need to recompile UIKit (or probably CoreData), which you cannot do.` that is very interesting part which I never thought about. So, does it basically mean that there is no any way to enable "safe" exception handling in ARC-enabled Cocoa Touch applications? – The Dreams Wind Jan 11 '23 at 06:29
  • @TheDreamsWind You can safely handle exceptions *that you throw* as long as you write exception-safe code around it. But just as ObjC `atomic` is [insufficient to make code thread-safe](https://stackoverflow.com/a/589392/97337), "doesn't leak ARC-managed memory" is insufficient to make code exception-safe. Cocoa is not exception-safe. When it throws exceptions, it does not promise that the system is left in a defined state. This is true in C++, too. If code you call is not exception-safe, `catch` won't save you. But you can write exception-safe ObjC for exceptions you throw (but also: don't). – Rob Napier Jan 11 '23 at 14:06