3

I have a test connection action logic. it will test the server in live or not. the bug happens when the logic finishes the test and try display UIAlertController inside closure thread it will crash the systems.

@IBAction func TestNetwork(_ sender: Any) {
    var message = "\(internetConnection) internet connection \n \(serverStatus) server\n  "
    self.showSpinner(onView: self.view)
    DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        self.testConnection(connectionTestCompletionHanlder: {connectionResult in


            let alertController = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
            let defaultAction = UIAlertAction(title: "OK", style: .default) { (action) in
                // Respond to user selection of the action.
            }
            alertController.addAction(defaultAction)
            //self.removeSpinner()
            self.present(alertController, animated: true){
                // The alert was presented
            }
        })
    }
}

error

-[Assert] Cannot be called with asCopy = NO on non-main thread. +[UIView setAnimationsEnabled:] being called from a background thread. Performing any operation from a background thread on UIView or a subclass is not supported and may result in unexpected and insidious behavior.
-Unsupported use of UIKit view-customization API off the main thread. -setHasDimmingView: sent to <_UIAlertControllerView: 0x7ffe2ff8b4a0; frame = (0 0; 375 667); layer = >

-Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'

developer16
  • 195
  • 12
  • 1
    If I follow your code flow correctly, it seems that you are not presenting the alertController on the main thread. You do `DispatchQueue.main.asyncAfter` to initially set things on the main thread, but then have another nested closure, the completion handler, which actually launches the alert controller which doesn't redirect to the main thread. Unless you know 100% that this completion handler executes on `main` I'd explicitly push the alert controller to it (especially as it won't do any harm either way).. – flanker Nov 06 '19 at 17:21
  • can you show me how to organize my code way because I am new to thread in iOS? and thank you :) – developer16 Nov 06 '19 at 17:29
  • Crash the system? Any error log message when that happens? But what's the code of `testConnection(connectionTestCompletionHanlder:)`? It's called on main thread, BUT there is no guarantee that `{ connectionResult in ...}` is called on main thread. Add there a `DispatchQueue.main.async{ code... self.present(alertController, animated: true) }` – Larme Nov 06 '19 at 17:31

2 Answers2

4

As per the comments above, you should always launch UI work on the main thread. The completion handler you have above, although instantiated on the main thread through the asyncAfter could execute on any thread, so you therefore need to change the section that opens the alert controller to explicitly be on the main thread:

//previous code as above...

DispatchQueue.main.async {
  self.present(alertController, animated: true){
    // The alert was present
  }
}

You could also set the alert controller's completion handler to nil if your are not planning to utilise it, rather than having an empty closure.

flanker
  • 3,840
  • 1
  • 12
  • 20
2

I had a similar issue. Answering here if someone else encounters it.

The app can also crash in iOS 13.3 (at least) when showing the alert to ask for permissions, when your app uses SceneDelegate even with the Application Scene Manifest entries are present in plist file. Had to remove the SceneDelegate and the related entries from the info.plist file, and no more crash.

Sergio
  • 2,346
  • 2
  • 24
  • 28