Reputation: 99
I've got a problem with a Realm result set in Swift. I am using a Realm notification block to update my TableView whenever the Realm results are updated.
However, during the update of the TableView, new items are sometimes added to the Realm results causing an NSInternalInconsistencyException
.
My environment:
Scenario:
So lets say I am sending a chat message and in the mean time receiving one too.
NSInternalInconsistencyException
because the size of the Realm results at the beginning of the update, differs from the size at the end of the update (since the received message was added to the Realm database).Exception that is thrown:
2016-11-16 21:03:57.727510 MyApp[9042:2331360] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (98) must be equal to the number of rows contained in that section before the update (97), plus or minus the number of rows inserted or deleted from that section (10 inserted, 10 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
*** First throw call stack:
(0x18f5021c0 0x18df3c55c 0x18f502094 0x18ff8f79c 0x19554001c 0x1000faeb8 0x10137619c 0x10134d4d0 0x100bf4b88 0x100bf4754 0x100b427fc 0x100b425f0 0x100ba0004 0x100d3072c 0x100d878b8 0x100d8840c 0x100d883e4 0x18f4b0278 0x18f4afbc0 0x18f4ad7c0 0x18f3dc048 0x190e62198 0x1953c82fc 0x1953c3034 0x100105be8 0x18e3c05b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
Steps to reproduce:
realm.objects(ChatMessage.self)
.NSInternalInconsistencyException
My view controller:
This is the code that causes the crash.
import UIKit
import RealmSwift
import Alamofire
import AlamofireObjectMapper
class ChatTestViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
let realm = try! Realm()
let textCellMeIdentifier = "ChatMessageCellMe"
var results: Results<ChatMessage>?
var ticket: Ticket?
var notificationToken: NotificationToken?
var tableViewTempCount = 0
override func viewDidLoad() {
super.viewDidLoad()
ticket = realm.objects(Ticket.self).first
results = realm.objects(ChatMessage.self)
initializeTableView()
notificationToken = results?.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
tableView.reloadData()
break
case .update(_, let deletions, let insertions, let modifications):
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .none)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .none)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .none)
tableView.endUpdates()
break
case .error(let error):
fatalError("\(error)")
break
}
}
_ = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.sendRandomMessage), userInfo: nil, repeats: true)
}
deinit {
notificationToken?.stop()
}
private func initializeTableView() {
tableView.register(UINib(nibName: textCellMeIdentifier, bundle: Bundle.main), forCellReuseIdentifier: textCellMeIdentifier)
tableView.delegate = self
tableView.dataSource = self
}
@objc func sendRandomMessage() {
tableViewTempCount += 1
let parameters: Parameters = [
"body": "Random Test: " + String(describing: tableViewTempCount),
]
let tempMessage = ChatMessage()
tempMessage.id = "-" + String(describing: tableViewTempCount)
tempMessage.ticketId = ticket!.id
tempMessage.sent = false
tempMessage.body = parameters["body"]! as! String
tempMessage.by = ChatMessage.By.me.rawValue
tempMessage.createdAt = Date()
let realm = try! Realm()
try! realm.write {
/// Add a temporary message to the TableView (and remove it if the server returned the saved message)
realm.add(tempMessage, update: true)
// _ = ChatMessageClient.create(ticket!, parameters) { (error: Error?, chatMessages: [ChatMessage]?) in
// /// The server responded with the message and it was inserted in our Realm database, so delete the temp message
// realm.delete(tempMessage)
// }
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = results?[indexPath.row]
let cell:ChatMessageCell = (tableView.dequeueReusableCell(withIdentifier: textCellMeIdentifier, for: indexPath as IndexPath) as? ChatMessageCell)!
cell.body.text = row?.body
cell.container.alpha = (row?.sent)! ? 1.0 : 0.4
return cell
}
}
I hope somebody can help me to prevent this exception being thrown.
Upvotes: 2
Views: 622
Reputation: 14409
We've recently fixed some race conditions in Realm's collection notification mechanism which could have caused the exception you're seeing here. See https://realm.io/news/realm-objc-swift-2.1/
Could you please try again with Realm Swift 2.1.0 and let us know here if it resolves your issue? Thanks!
Upvotes: 3