【问题标题】:CoreData Concurrency issueCoreData 并发问题
【发布时间】:2016-06-02 09:10:59
【问题描述】:

我在使用private managedObjectContext在后台保存数据时遇到问题。我是 CoreData 的新手。我正在为NSManagedObjectContext 使用父子方法,但面临几个问题。

多次点击重新加载按钮时出现错误

错误:

  1. 'NSGenericException',原因:集合 <__nscfset:> 在枚举时发生了变异

  2. 有时:在这里崩溃try managedObjectContext.save()

  3. 有时键值编码兼容错误

我的 ViewController 类

        class ViewController: UIViewController {
            var jsonObj:NSDictionary?
            var values = [AnyObject]()
            @IBOutlet weak var tableView:UITableView!
        
            override func viewDidLoad() {
                super.viewDidLoad()
                getData()
                saveInBD()
                NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.saved(_:)), name: "kContextSavedNotification", object: nil)
            }
   //Loding json data from a json file
    
           func getData(){
            if let path = NSBundle.mainBundle().pathForResource("countries", ofType: "json") {
            do {
            let data = try NSData(contentsOfURL: NSURL(fileURLWithPath: path), options: NSDataReadingOptions.DataReadingMappedIfSafe)
            
            
            do {
            jsonObj =  try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
                
            } catch {
            jsonObj = nil;
            }
            
            
            } catch let error as NSError {
            print(error.localizedDescription)
            }
            } else {
            print("Invalid filename/path.")
            }
            }
           **Notification reciever**
    
            func saved(not:NSNotification){
                dispatch_async(dispatch_get_main_queue()) {
                    if let data  = DatabaseManager.sharedInstance.getAllNews(){
                        self.values = data
                        print(data.count)
                        self.tableView.reloadData()
                        
                    }
          
                }
                }
           
            func saveInBD(){
                if jsonObj != nil {
                    guard let nameArray = jsonObj?["data#"] as? NSArray else{return}
                    DatabaseManager.sharedInstance.addNewsInBackGround(nameArray)
                }
            }
            //UIButton for re-saving data again

            @IBAction func reloadAxn(sender: UIButton) {
                saveInBD()
            }
        
    }
    
    
    **Database Manager Class**
    
    public class DatabaseManager{
        
        static  let sharedInstance = DatabaseManager()
        
        let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
        
        private init() {
        }
        
        func addNewsInBackGround(arr:NSArray)  {
            let jsonArray = arr
            let moc = managedObjectContext
            
            let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
            privateMOC.parentContext = moc
           
                privateMOC.performBlock {
                    for jsonObject in jsonArray {
                        let entity =  NSEntityDescription.entityForName("Country",
                            inManagedObjectContext:privateMOC)
                        
                        let managedObject = NSManagedObject(entity: entity!,
                            insertIntoManagedObjectContext: privateMOC) as! Country
                        
                        managedObject.name = jsonObject.objectForKey("name")as? String
                        
                    }
                    
                    
                    do {
                        try privateMOC.save()
                        
                        self.saveMainContext()
                        
                        NSNotificationCenter.defaultCenter().postNotificationName("kContextSavedNotification", object: nil)
                    } catch {
                        fatalError("Failure to save context: \(error)")
                    }
                }
                           
        }
        
        
        
        
        
        func getAllNews()->([AnyObject]?){
            let fetchRequest = NSFetchRequest(entityName: "Country")
            fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType
            
            do {
                let results =
                    try managedObjectContext.executeFetchRequest(fetchRequest)
                results as? [NSDictionary]
                if results.count > 0
                {
                    return results
                }else
                {
                    return nil
                }
            } catch let error as NSError {
                print("Could not fetch \(error), \(error.userInfo)")
                return nil
            }
        }
        
        func saveMainContext () {
            if managedObjectContext.hasChanges {
                do {
                    try managedObjectContext.save()
                } catch {
                    let nserror = error as NSError
                    print("Unresolved error \(nserror), \(nserror.userInfo)")
                }
            }
        }
    }

【问题讨论】:

标签: ios swift core-data concurrency nsmanagedobjectcontext


【解决方案1】:

可以在后台写入并在主线程中读取(像您一样使用不同的 MOC)。实际上你几乎做对了。

应用在try managedObjectContext.save() 行崩溃,因为saveMainContext 是从私有MOC 的performBlock 中调用的。修复它的最简单方法是将保存操作包装到另一个performBlock

func saveMainContext () {
    managedObjectContext.performBlock {
        if managedObjectContext.hasChanges {
            do {
                try managedObjectContext.save()
            } catch {
                let nserror = error as NSError
                print("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

其他两个错误有点棘手。请提供更多信息。什么对象不符合什么键的键值?这很可能是 JSON 解析问题。

第一个错误(“枚举时发生突变”)实际上是一个讨厌的错误。描述非常简单:一个集合被一个线程变异,而另一个线程被枚举。它发生在哪里? 一个可能的原因(我会说很可能是一个)是它确实是一个核心数据多线程问题。尽管您可以使用多个线程,但您只能在获取它们的线程内使用核心数据对象。如果你将它们传递给另一个线程,你很可能会遇到这样的错误。

查看您的代码并尝试找到可能发生这种情况的地方(例如,您是否从其他类访问self.values?)。不幸的是,我没能在几分钟内找到这样的地方。如果你说这个错误发生在哪个集合枚举上,那会有所帮助)。

更新: 附言我只是认为该错误可能与saveMainContext 函数有关。它在调用saved 之前执行。 saveMainContext 在后台线程上执行(我的意思是在原始代码中),saved 在主线程上执行。所以在修复 saveMainContext 之后,错误可能会消失(不过我不是 100% 确定)。

【讨论】:

  • 我是否应该从私有块中调用 saveMainContext() 并按照您的建议 managedObjectContext.performBlock {} 仅在定义的地方使用。
  • @NA000022,是的,它应该可以工作。另一种方法是保持saveMainContext 方法本身不变,但将对它的调用包装到dispatch_async(dispatch_get_main_queue()) 中。第一种情况在我看来更好,因为在第二种情况下,我们必须记住在调用它时包装这个函数。我们的目标是确保我们只在主线程上调用managedObjectContext.save()。我们这样做的方式并不那么重要。
  • 它运行良好,但现在我面临另一个问题。如果我点击用于将数据保存在私有 MOC 中的 reloadAction(),UI 会挂起 1 秒,并且随着记录的增长而不断增加。
  • @NA000022,处理此类问题的正确方法是使用 Instruments / Time Profiler 在 UI 挂起时在主线程上查找需要很长时间才能完成的代码。但鉴于它是在最新修复后开始的,最可能的原因实际上是主要的 MOC 保存操作。保存到磁盘很慢。有一种解决方法。您可以创建另一个私有 MOC(我们称之为 SavingMOC)。 mainMOC.parentContext = savingContext。像这样:The Big Nerd Ranch Core Data Stack.
【解决方案2】:

你违反了线程限制。

您不能在后台写入 CoreData,并在 MainThread 中读取。

对CoreData的所有操作都必须在同一个线程中完成

【讨论】:

  • 如何使用同一线程,因为私有队列在不同线程上运行
  • 创建一个引用dispatch_queue_create(),然后dispatch_async()执行里面的代码
  • 所以我应该创建自己的线程并在上面做所有事情。
  • 确实如此,但我强烈建议您不要这样做。您应该改为在主胎面中执行所有操作
  • 但我认为父子上下文使用其单独的线程来保存数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 2017-05-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 2011-06-07
  • 2012-10-26
相关资源
最近更新 更多