我正在制作一个从数据库中读取消息的程序。
程序每10秒将数据下载到一个数组中。
但有时会出现问题..有时我的应用随机会抛出错误“致命错误:索引超出范围”
这是(我认为)因为有时URLSession会给myMessages.messages返回null值,或者我不知道:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myMessages.messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myOwnCell", for: indexPath) as! MyOwnCell
// Sometimes here occours a bug:indexpath.row and myMessages.messages.count are equal to 0
let message = myMessages.messages[indexPath.row]
}
...
我试了一下:
打印出myMessages.messages,myMessages.messages.count和indexPath
在正常情况下,结果如下(带有2条消息):
[<App.BubbleMessage: 0x1c066bc40>, <App.BubbleMessage: 0x1c0678e40>] //messages
2 //messages count
[0, 0] //indexPath
[<App.BubbleMessage: 0x1c066bc40>, <App.BubbleMessage: 0x1c0678e40>]
2
[0, 1]
然后有时会出现错误:
[]
0
[0, 0]
这会产生错误。有一些工作吗?:C
更新:
如果我手动删除myMessages.messages中的所有邮件,那么它仍在运行
[] //messages
0 //messages count
并且不会调用cellForRowAt函数..所以我不知道
为什么会这样,如果我手动创建一个空数组,那么它就不会调用cellForRowAt函数,
但有时即使数组仍为空,它也会调用它并抛出错误。为什么呢?
有没有办法防止它发生?
if (myMessages.messages.count == 0){ return }
更新2 :有可能
在数组中加载了2条消息(msg1,msg2)
现在cellForRowAt函数生成第一个函数:msg1
但是当它生成msg1时,从服务器加载的数据仅以某种方式包含1条消息 - &gt;因此,myMessagges.messages将被删除所有()并仅使用1个值进行更新。
所以下次当cellForRowAt在rowNumber 2中查找msg2时,它找不到它。
我该如何解决?
答案 0 :(得分:0)
您在DispatchQueue.main.async
回调中使用URLSession
吗?
URLSession
使用后台线程来调用API,而UI必须位于主线程(调度队列)上。
第二个问题,如果你真的使用DispatchQueue.main.async
,可能会有另一个问题是线程竞赛。
想象一下以下场景
// 1
myMessages.messages.append(contentsOf: [message1, message2])
DispatchQueue.main.async {
// 2
tableView.insertRows( /* put here code for insertion of two rows */)
}
// 3
myMessages.messages.append(message3)
DispatchQueue.main.async {
// 4
tableView.insertRows( /* put here code for insertion of one rows */)
}
会发生什么?
1 is executed
then 3
then 2 then 4
这意味着
- append to the backend array 2 object
- then append to the backend array 1 object
(now the array has 3 objects inserted, not 2)
- then inform the tableview that 2 objects are inserted
- then inform the tableview that another object is inserted
这会使表视图崩溃,当他被告知只添加2个对象时后端(当他调用numberOfRowsInSection时)得到一个不同的数字{3 inserted}
解决方案是使用一点不变性。获取数组的快照并将其传递给调度队列,成为tableview的新后端。
// 1
myMessages.messages.append(contentsOf: [message1, message2])
let snapshot = myMessages.messages
DispatchQueue.main.async {
// 2
self.backendArray = snapshot // Just take the snapshot that is
// captured from the out of the closure
tableView.insertRows( /* put here code for insertion of two rows */)
}
// 3
myMessages.messages.append(message3)
// Take another snapshot, must be different from the first
let snapshot2 = myMessages.messages
DispatchQueue.main.async {
// 4
self.backendArray = snapshot2 // Just take the second snapshot that is
// captured from the out of the
tableView.insertRows( /* put here code for insertion of one rows */)
}
使用backendArray
作为后端(在numberOfRows和cellForRowAt中)而不是真实对象。
现在每次调用dispatch main时都会获取数组的快照,因此即使计算/获取仍然有更多,您的tableview数据源也将与UI一致