在聊天应用程序中集成核心数据时面临的问题

时间:2018-07-20 12:57:31

标签: swift multithreading core-data socket.io nsfetchedresultscontroller

在将coredata与我们的聊天应用程序集成时,我们面临着问题。请帮助我们解决问题。我们试图逐一找出每个问题,但有时会得到解决,然后随机出现。我们正试图从上一个星期开始对其进行修复。

我们的设置堆栈

我们正在使用套接字库进行实时聊天。为了持久化数据,我们使用核心数据。我们的应用程序支持iOS 8及更高版本,因此我们不能使用PersistenceContainer,因此要解决此问题,我们使用的是BNRCoreDataStack [url:https://github.com/bignerdranch/CoreDataStack],与PersistenceContainer的功能类似。

我们还使用IGListKit来显示聊天,并且我们已经创建了viewModels以避免将可变的coredata对象发送到IGLIstkit,因为IGListkit在不可变模型上可以正常工作。另外,我们还使用此设置来创建自己的viewModels [url:https://github.com/Instagram/IGListKit/blob/master/Guides/Working%20with%20Core%20Data.md]

我们面临的问题

1]约束验证失败

2] FetchResult控制器崩溃问题

崩溃日志:

2018-07-19 21:41:36.515153+0530 Toppr Doubts[62803:2359707] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.54.4/UITableView.m:2012
2018-07-19 21:41:36.517093+0530 Toppr Doubts[62803:2359707] [error] error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (60) must be equal to the number of rows contained in that section before the update (50), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (60) must be equal to the number of rows contained in that section before the update (50), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

3]非法尝试在不同上下文中的对象之间建立关系“ lastMessage”

下面是我们的CoreDataModel,我们没有对Message使用任何抽象实体,因为我们知道这可能会导致很多性能问题。

core-data-model-image

会话实体

我们正在fromJSON方法中创建对象,其余实体的设置相同。我也分享了LastMessage Entity,以展示我们整合关系的方式,对其他人同样如此

public class Session: NSManagedObject {
     class func fromJSON(_ json: JSON, moc: NSManagedObjectContext) -> Session? {


    if let entityDescription = NSEntityDescription.entity(forEntityName: "Session", in: moc) {
        // Object creation
        let object = Session(entity: entityDescription, insertInto: moc)

        object.internalIdentifier = json[kSessionIdKey].int64Value

        if let date = json[kSessionStartedOnKey].dateTime as NSDate? {
            object.startedOn = date
        }

        if let date = json[kSessionEndedOnKey].dateTime as NSDate? {
            object.endedOn = date
        }

        object.statusType = SessionStatus(rawValue: json[kSessionStatusKey].stringValue).map { $0.rawValue } ?? SessionStatus.none.rawValue
        object.stateType = SessionState(rawValue: json[kSessionStateKey].stringValue).map { $0.rawValue } ?? SessionState.none.rawValue

        if let ratingDict = json[kSessionRatingKey].dictionaryObject {
            if let rating = ratingDict["student"] as? Int {
                object.rating = rating
            }
        }

        object.subjectID = json[kSessionSubjectKey]["id"].intValue

        // Subjects are already stored need to fetch and assign
        let subjectFetchRequest: NSFetchRequest<Subject> = Subject.fetchRequest()
        subjectFetchRequest.predicate = NSPredicate(format: "internalIdentifier == %d", Int64(json[kSessionSubjectKey]["id"].intValue))

        do {
            if let subject = try moc.fetch(subjectFetchRequest).first {
                object.subject = subject
            }

        } catch let erroe as NSError {
            Logger.log.error(error)
        }

        // Student Object initialisation
        if json[kSessionStudentKey] != JSON.null {
            if let student = Student.fromJSON(json[kSessionStudentKey], moc: moc) {
                object.student = student
            }
        }

        // Tutor Object initialisation
        if json[kSessionTutorKey] != JSON.null {
            if let tutor = Tutor.fromJSON(json[kSessionTutorKey], moc: moc) {
                object.tutor = tutor
            }
        }

        // LastMessage Object initialisation
        if json[kSessionLastMessageKey] != JSON.null {
            if let lastMessage = LastMessage.fromJSON(json[kSessionLastMessageKey], moc: moc) {
                object.lastMessage = lastMessage
            } else {
                return nil
            }
        }

        return object
    }
    return nil
  }
}

LastMessage

public class LastMessage: NSManagedObject, Message {

  class func fromJSON(_ json: JSON, moc: NSManagedObjectContext) -> LastMessage? {

    if let entityDescription = NSEntityDescription.entity(forEntityName: "LastMessage", in: moc) {
        let object = LastMessage(entity: entityDescription, insertInto: moc)
        object.id = json[kMessageIdKey].intValue
        object.body = json[kBodyKey].stringValue
        object.type = MessageType(rawValue: json[kTypeKey].stringValue) ?? MessageType.none

        object.doubtTag = json[kDoubtTagKey].stringValue

        if json[kAttachmentKey] != JSON.null {
            if let attachment = Attachment.fromJSON(json[kAttachmentKey], moc: moc) {
                object.attachment = attachment
            }
        }

        if let date = json[kSentOnKey].dateTime as NSDate? {
            object.sentOn = date
        }

        if json[kSentByKey] != JSON.null {
            if let sentBy = SentBy.fromJSON(json[kSentByKey], moc: moc) {
                object.sentBy = sentBy
            }
        }

        object.deliveryState = DeliveryState(rawValue: json[kDeliveryStateKey].stringValue) ?? DeliveryState.none
        object.sessionId = json[kSessionIdKey].intValue
        return object
    }
    return nil
  }

}

获取用户状态 帮助我们获取主题和实时聊天的数据。

static func getUserState(completion:@escaping (_ success: Bool) -> Void) {

    SocketManager.sharedInstance.send(eventName: .userState) { (response) in
        guard !response.isError() else { return completion(false) }


        // Coredatastack
        guard let coreDataStack = (UIApplication.shared.delegate as! AppDelegate).coreDataStack else { return }
        let wmoc = coreDataStack.newChildContext()

        // Save Subject and Live Sessions
        let subjects = response.result["subjects"].arrayValue.flatMap({ Subject.fromJSON($0, moc: wmoc) })
        let sessions = response.result["live_sessions"].arrayValue.flatMap({ Session.fromJSON($0, moc: wmoc) })

        if sessions.isNotEmpty || subjects.isNotEmpty {
            CoreDataStack.batchUpdate(moc: wmoc, completion: {
                NotificationCenter.default.post(name: NSNotification.Name("didUpdateUserState"), object: nil)
                completion(true)
            })
        }
        completion(false)
    }
}

获取上一个会话 帮助我们获取非活动聊天的数据。在存储会话时我们遇到了问题

static func getPreviousSessions(isLoadingMore: Bool, completion: @escaping (_ success: Bool,_ isMore: Bool)->Void) {
    guard let coreDataStack = (UIApplication.shared.delegate as! AppDelegate).coreDataStack else { return }
    let wmoc = coreDataStack.newChildContext()

    var sessionID = 0

    // TODO: - Need to implement last sessionID from CoreData
    if isLoadingMore {
        // Get Sessions with Status == closed order by sessionID asc
        let archiveSession = Session.filterArchivedSessions(moc: wmoc)
        let sortedArchiveSession = archiveSession?.sorted(by: { $0.0.id < $0.1.id })

        // Get last sessionID
        if let lastSessionID = sortedArchiveSession?.first?.id {
            sessionID = lastSessionID
        }
    }

    let request: [String: Any] = [ "last_session_id": sessionID ]

    SocketManager.sharedInstance.send(request, eventName: .getPreviousSessions) { (response) in

        if response.result.isEmpty {
            completion(false, false)
        } else {
            let sessions = response.result["sessions"].arrayValue.flatMap({ Session.fromJSON($0, moc: wmoc) })
            if sessions.isNotEmpty {
                CoreDataStack.batchUpdate(moc: wmoc)
            } else {
                for session in sessions { wmoc.delete(session) }
            }

            if let isMore = response.result["is_more_server"].bool {
                completion(true, isMore)
            }
        }
    }
}

tableview-coredata

在上图中,每个会话都假定有最后一条消息和一个主题。但是如您所见,没有最后一条消息,有些会话的主题为空

CoreDataStack +扩展 要将数据直接保存到商店

extension CoreDataStack {
 // This method will add or update a CoreData's object.
 static func batchUpdate(moc: NSManagedObjectContext? = nil, completion: (()-> Void)? = nil) {
    guard let moc = moc, moc.hasChanges else { return }

     if #available(iOS 10.0, *) {
        moc.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
    }

    do {
        try moc.performAndWaitOrThrow {
            try moc.saveContextToStoreAndWait()
            DispatchQueue.main.async {
                completion?()
            }
        }
    } catch {
        print("Error creating initial data: \(error)")
    }
  }
}

NSFetchedResultsController设置

lazy var archievedSessionFRC: NSFetchedResultsController<Session> = {

    guard let coreDataStack = (UIApplication.shared.delegate as! AppDelegate).coreDataStack else { return NSFetchedResultsController() }

    // Create Fetch Request
    let fetchRequest: NSFetchRequest<Session> = Session.fetchRequest()

    // Configure Fetch Request
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "internalIdentifier", ascending: false)]

    fetchRequest.predicate = NSPredicate(format: "statusType = %@", "closed")

    let archievedSessionFRC = NSFetchedResultsController(
        fetchRequest: fetchRequest,
        managedObjectContext: coreDataStack.mainQueueContext,
        sectionNameKeyPath: nil,
        cacheName: nil)

    archievedSessionFRC.delegate = self

    return archievedSessionFRC

}()

override func viewDidLoad() {
    super.viewDidLoad()

    setupViews()
    do {
        try archievedSessionFRC.performFetch()

        if let sessions = archievedSessionFRC.fetchedObjects {
            self.previousSessions = sessions
        }
    } catch {
        let fetchError = error as NSError
        print("\(fetchError), \(fetchError.localizedDescription)")
    }
}
// MARK: - NSFetchedResultsControllerDelegate
extension HomeVC: NSFetchedResultsControllerDelegate {
    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        self.tableView.beginUpdates()
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        switch type {
            case .insert:
                if let indexPath = newIndexPath {
                     self.tableView.insertRows(at: [indexPath], with: .automatic)
                 }
            case .delete:
                if let indexPath = indexPath {
                    self.tableView.deleteRows(at: [indexPath], with: .automatic)
                }
            case .move:
                if let indexPath = indexPath , let newIndexPath = newIndexPath {
                     self.tableView.moveRow(at: indexPath, to: newIndexPath)
                }
            case .update:
                 if let indexPath = indexPath {
                    self.tableView.reloadRows(at: [indexPath], with: .automatic)
                }
        }       
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {        
        self.tableView.endUpdates()
    }
}

预先感谢

0 个答案:

没有答案