Swift 5异步调用触发NSInternalInconsistencyException异常修复求助
嘿,这个问题我太熟悉了!你碰到的NSInternalInconsistencyException错误,本质原因是iOS的UI操作必须严格在主线程执行,而你的代码里异步请求的回调逻辑跑到了后台线程,导致弹出弹窗(present操作)这类UI修改触发了线程违规。
问题根源拆解
看你的代码链路:
- Controller层的
Marcatura方法用了DispatchQueue.global(qos: .userInteractive).async把网络请求逻辑放到了后台线程 - Model层的网络请求本身就是异步的,最终的completion回调会沿着这个后台线程传递到View层
- View层里的
present(alertController...)操作直接在后台线程执行——这直接违反了UIKit的线程安全规则
具体修复方案
有两种简洁的修复方式,选一种就行:
方式1:在View层的回调里强制切回主线程
把View层completion闭包里所有UI相关的代码,全部包裹在DispatchQueue.main.async中,确保UI操作在主线程执行:
self.u.Marcatura(Longitudine: Float(self.Longitudine)!, Latitudine: Float(self.Latitudine)!, Stato: Stato.description, completion: { result in // 关键:把所有UI操作切回主线程 DispatchQueue.main.async { if(result != "true") { let MessageError = "Errore: " + result let alertController = UIAlertController(title: "Errore", message: MessageError, preferredStyle: .alert) let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(OKAction) self.present(alertController, animated: true, completion: nil) } else { var Message = "" switch Stato { case .Ingresso: Message = "Ingresso registrato" case .Uscita: Message = "Uscita registrata" case .Arrivo: Message = "Arrivo in cantiere" case .Partenza: Message = "Partenza dal cantiere" } let alertController = UIAlertController(title: "MarcaTempo Aggiornato", message: Message, preferredStyle: .alert) let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(OKAction) self.present(alertController, animated: true, completion: nil) } } })
另外,你最后一行的self.ChangeButtonColor(color: UIColor.white)如果是在DispatchQueue.main.asyncAfter的外面,也要确保它在主线程执行(不过看起来它本来就在主线程,若有疑虑也可以套个main.async)。
方式2:在Model层统一把回调切回主线程
如果不想在每个View层的回调里都写主线程代码,可以直接在Model层的网络请求回调里切到主线程,这样所有上层的completion都会自动在主线程执行:
func Marcatura(IdUtente: Int, Longitudine: Float, Latitudine: Float, Stato: String, completion: @escaping (String) -> ()) { let jsonrequest = JSON() let jsonarray: [String: Any] = ["IdUtente": IdUtente, "Longitudine": Longitudine, "Latitudine": Latitudine, "Stato": Stato] jsonrequest.GetSingleString(Router: "/Utente/Marcatura", ValueArray: jsonarray, completion: { result in // 在这里统一切回主线程,上层无需再处理线程问题 DispatchQueue.main.async { completion(result) } }) }
同时,Controller层里那个多余的DispatchQueue.global(qos: .userInteractive).async可以删掉——网络请求本身已经是异步的,没必要再套一层后台线程,反而会增加线程切换的复杂度:
func Marcatura(Longitudine: Float, Latitudine: Float, Stato: String, completion: @escaping (String) -> ()) { let rtemp = UserModel() var IdUtenteMarcatura: Int = Int(self.GetIdUtente()) if(self.IdUtente > 0) { IdUtenteMarcatura = self.IdUtente } rtemp.Marcatura(IdUtente: IdUtenteMarcatura, Longitudine: Longitudine, Latitudine: Latitudine, Stato: Stato, completion: { result in completion(result) }) self.IdUtente = 0 }
为什么这样能解决问题?
UIKit框架不是线程安全的,所有涉及UI布局、控件状态修改的操作,都必须在主线程执行。后台线程修改UI会导致布局引擎的内部状态混乱,从而触发NSInternalInconsistencyException这个异常。通过把UI操作强制切回主线程,就能保证这些操作在正确的线程上下文里执行,避免线程违规。
内容的提问来源于stack exchange,提问作者Riki

