我正在使用Realm和MessageKit框架在我的应用程序中实现聊天。 MessageKit在内部使用UICollectionView,每个聊天消息都放置在集合视图的自己部分中。
我的聊天消息模型如下:
class ChatMessage: Object {
@objc dynamic var id: Int = generateMessageId()
@objc dynamic var timestamp = Date()
@objc dynamic var senderId: Int = -1
@objc dynamic var text: String = ""
var recipients = List<Recipient>()
@objc dynamic var receivedByServer = false
var readReceipts = List<Recipient>()
}
在我的视图控制器中,我有一系列聊天消息声明如下:
var messages: Results<ChatMessage>!
// and in viewDidLoad()...
messages = conversation.messages.sorted(byKeyPath: "timestamp", ascending: true)
此数组用作UICollectionView基础MessageKit的数据源。我正在观察此数组的更改,当我检测到发生更改时,我将按照以下方式相应地更新UICollectionView的各个部分:
notificationToken = messages.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
print("Messages collection has been populated...")
case .update(_, let deletions, let insertions, let modifications):
print("Messages collection has been updated...")
self?.messagesCollectionView.performBatchUpdates( {
if modifications.count > 0 {
print("modifications count: \(modifications.count)")
self?.messagesCollectionView.reloadSections(IndexSet(modifications))
}
if deletions.count > 0 {
print("deletions count: \(deletions.count)")
self?.messagesCollectionView.deleteSections(IndexSet(deletions))
}
if insertions.count > 0 {
print("insertions count: \(insertions.count)")
self?.messagesCollectionView.insertSections(IndexSet(insertions))
}
}, completion: { [ weak self] _ in
print("Scroll to bottom of UICollectionView...")
self?.messagesCollectionView.scrollToBottom(animated: true)
})
case .error(let error):
print("Error occurred while observing messages: \(error)")
}
}
最初,此代码似乎可以正常工作,但是随后我开始看到一个问题,该问题似乎与很快发生的多个更新有关。例如,当我发送聊天消息时,服务器将确认已收到该消息,这将导致修改聊天消息对象。当收件人阅读消息时,这也将导致聊天消息对象被更新。在某些情况下,这两个事件会很快发生,因此我认为调用messagesCollectionView.reloadSections(IndexSet(modifications))的代码将运行两次。似乎第二个呼叫实际上是在第一个呼叫仍在处理中时发生的,这导致崩溃。或者至少我认为这是导致问题的原因。
当崩溃发生时,这就是我从Xcode得到的东西:
[CollectionView] An attempt to prepare a layout while a prepareLayout call was already in progress (i.e. reentrant call) has been ignored. Please file a bug. UICollectionView instance is (<MessageKit.MessagesCollectionView: 0x10c01b400; baseClass = UICollectionView; frame = (0 0; 414 896); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x2830aa670>; layer = <CALayer: 0x283ea44a0>; contentOffset: {0, 137}; contentSize: {414, 604}; adjustedContentInset: {88, 0, 429, 0}> collection view layout: <MessageKit.MessagesCollectionViewFlowLayout: 0x10b7069d0>)
[CollectionView] An attempt to update layout information was detected while already in the process of computing the layout (i.e. reentrant call). This will result in unexpected behaviour or a crash. This may happen if a layout pass is triggered while calling out to a delegate. UICollectionViewFlowLayout instance is (<MessageKit.MessagesCollectionViewFlowLayout: 0x10b7069d0>)
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndexedSubscript:]: index 0 beyond bounds for empty array'
使用Realm集合作为数据源并观察该集合的更改时,这是更新UICollectionView的正确方法还是我做错了所有这些?顺便说一句,由于MessageKit默认将每个聊天消息放入其自己的部分中,因此我正在更新部分(相对于项目)。