【问题标题】:Call Api with Alamofire and store data into Coredata使用 Alamofire 调用 Api 并将数据存储到 Coredata
【发布时间】:2022-07-04 14:48:50
【问题描述】:

我有一个类来管理数据库,我在其中创建了将数据更新到特定实体的功能,但是当我使用分页调用 api 时,每次调用都会给我 50 个数据,在获取数据后,我将这些数据更新到我的表中,但是当时 UI 冻结由于将所有任务执行到主队列中,我已经看到了许多解决方案,但不完全了解如何通过不干扰 UI 任务进入后台队列和所有来做到这一点。

所以为什么我需要将所有进程都放入后台队列,即使不干扰 UI 任务,并且如果我的应用程序进入后台模式也可以工作。

我的应用程序委托文件我有这个:-

// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "Name")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

// MARK: - Core Data Saving support

func saveContext (completionBlock: @escaping ((Bool) -> ())) {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
            completionBlock (true)
        } catch {
            let nserror = error as NSError
            completionBlock (false)
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

我的数据库管理器有这个功能可以将数据保存在 coredata 中:-

 func addUpdateState(stateArray: [StateInfo], completionBlock: @escaping ((Bool) -> ())) {
    let localStateArray = self.getStatesData()
    for state in stateArray {
        let isContained = localStateArray.contains(where: {$0.id == state.id})

        var stateDetail = StateMO()
        if isContained == false {
            //ADD STATE DATA
            if let stateEntity = NSEntityDescription.entity(forEntityName: self.STATE_ENTITY, in: context) {
                stateDetail = NSManagedObject (entity: stateEntity, insertInto: context) as! StateMO
            }
        } else {
            //UPDATE STATE DATA
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: self.STATE_ENTITY)
            fetchRequest.predicate = NSPredicate(format: "id == %@", state.id ?? "")
            do {
                let results = try context.fetch(fetchRequest)
                if results.count != 0 {
                    stateDetail = results.first as! StateMO
                }
            } catch {

            }
        }
        stateDetail.id = state.id
        stateDetail.state_name = state.stateName
        stateDetail.state_code = state.statecode
        stateDetail.createdAt = state.createdAt
        stateDetail.updatedAt = state.updatedAt
    }
    APP_DELEGATE.saveContext { result in
        completionBlock (result)
    }
}

我的 Api Manager 函数通过 alamofire 请求从服务器获取数据:-

func SyncStateList(with params: [String: Any], success: @escaping (_ result: Bool, _ message:String, _ response: [String: AnyObject]) -> () ,failure: @escaping (_ error: Error?) -> ()) {
    let url = URLUtility.getURL(apiKey: ApiEndPoint.syncState)
    let headers: HTTPHeaders = [
        "token": currentUser?.token ?? ""
    ]
    if Connectivity.isConnectedToInternet(){
        AF.request(url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
            if response.response?.statusCode == 401
            {
                SVProgressHUD.dismiss()
                showAlertMessage(title: self.unauthorizedMessage, "") {
                    LogoutClearEntireData()
                }
            } else if response.response?.statusCode == 200 {
                if let json = response.value {
                    if let response = json as? [String: AnyObject] {
                        if response.success == true {
                            success(response.success,response.message,response)
                        } else {
                            success(false,response.message,response)
                        }
                    } else {
                        failure(response.error)
                    }
                } else {
                    failure(response.error)
                }
            } else {
                failure(response.error)
            }
        }.responseString { (responseString) in
            printLog(responseString.description)
        }
    } else {
        SVProgressHUD.dismiss()
        showAlertMessage(title: "Unable to connect.", "Please check your internet connection.", complition: nil)
    }
}

调用函数获取所有带分页的状态数据,并在开始同步时通过调用此函数存储到核心数据中:-

func SyncStateList(completionBlock: @escaping ((_ success:Bool) -> ())) {
    let params: [String: Any] = ["date": lastModifiedDateForSyncState]
    APIManager.shared.SyncStateList(with: params) { success, message, response in
        if success == true {
            if let dataResponse = response["data"] as? [String: Any] {
                if let stateData = StateResponse(JSON: dataResponse) {
                    self.stateData = stateData
                }
            }
            lastModifiedDateForSyncState = self.stateData?.lastModifiedDate ?? ""
            if self.stateData?.isMoreRecordAvalilable == 1 {
                DatabaseManager.shareInstance.addUpdateState(stateArray: self.stateData?.states ?? []) { result in
                    self.SyncStateList(completionBlock: completionBlock)
                }
            } else {
                if self.stateData?.states.count != 0 {
                    DatabaseManager.shareInstance.addUpdateState(stateArray: self.stateData?.states ?? []) { result in
                        completionBlock(true)
                    }
                } else {
                    completionBlock(true)
                }
            }
        } else {
            printLog(message)
            completionBlock(false)
        }
    } failure: { error in
        printLog(error?.localizedDescription ?? "")
        completionBlock(false)
    }
}

【问题讨论】:

    标签: swift multithreading core-data alamofire


    【解决方案1】:

    对我来说,你是否在主线程中调用这些方法并不是很清楚,如果你有一个大数据集,它可能会冻结 UI,这可能就是这种情况。

    所以我的建议是在后台线程中执行 CoreData 操作并在主线程中更新 UI。

    func addUpdateState(stateArray: [StateInfo], completionBlock: @escaping ((Bool) -> ())) {
        
        DispatchQueue.global().async { // sent to background
            
            let localStateArray = self.getStatesData()
            for state in stateArray {
                let isContained = localStateArray.contains(where: {$0.id == state.id})
                
                var stateDetail = StateMO()
                if isContained == false {
                    //ADD STATE DATA
                    if let stateEntity = NSEntityDescription.entity(forEntityName: self.STATE_ENTITY, in: context) {
                        stateDetail = NSManagedObject (entity: stateEntity, insertInto: context) as! StateMO
                    }
                } else {
                    //UPDATE STATE DATA
                    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: self.STATE_ENTITY)
                    fetchRequest.predicate = NSPredicate(format: "id == %@", state.id ?? "")
                    do {
                        let results = try context.fetch(fetchRequest)
                        if results.count != 0 {
                            stateDetail = results.first as! StateMO
                        }
                    } catch {
                        // should handle this error here !!!
                    }
                }
                stateDetail.id = state.id
                stateDetail.state_name = state.stateName
                stateDetail.state_code = state.statecode
                stateDetail.createdAt = state.createdAt
                stateDetail.updatedAt = state.updatedAt
            }
            
            DispatchQueue.main.async { // APP_DELEGATE should be handled in the main thread
            // also it would be a better practice to create a separate class instead of keeping this in the AppDelegate.
                    APP_DELEGATE.saveContext { result in
                        completionBlock (result)
                    }
                }
            }
        }
    

    我希望这会有所帮助!

    【讨论】:

    • NSManagedObjectContext 不是线程安全的,只能在创建它的同一队列上使用。在两个不同的队列上使用相同的上下文是程序员的错误。如果您只有一个仅用于主队列的viewContext,请改用持久化容器的performBackgroundTask(_:) 方法。
    猜你喜欢
    • 1970-01-01
    • 2020-03-03
    • 2021-10-21
    • 2017-06-04
    • 1970-01-01
    • 1970-01-01
    • 2011-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多