创建新的 NSManagedObject 并将其分配给新的 NSManagedObject *有时*会失败

Posted

技术标签:

【中文标题】创建新的 NSManagedObject 并将其分配给新的 NSManagedObject *有时*会失败【英文标题】:Creating and assigning a new NSManagedObject to a new NSManagedObject *sometimes* fails 【发布时间】:2016-02-11 22:55:42 【问题描述】:

我正在创建一个名为“translation”的新 NSManagedObject。在翻译中,我需要创建两个额外的 NSManagedObjects,称为“短语”。有时其中一个短语分配会引发错误,但是当我检查这些值时,它们看起来都像是被创建得很好。什么给了???

创建翻译对象:

func getOrCreateTranslation(package: Package?, data: NSDictionary) -> Translation 
        let translationId = data["id"] as! NSNumber

        if let translation = self.getTranslation(translationId) 

            return translation

         else 

            let context = LocalDataStorage().context
            let translation = NSEntityDescription.insertNewObjectForEntityForName("Translation", inManagedObjectContext: context) as! Translation
            translation.id = translationId

            let fromPhrase = data["from_phrase"]! as! NSDictionary
            let toPhrase = data["to_phrase"]! as! NSDictionary


            let pm = PhraseManager()

            //*******
            // *SOMETIMES* ONE OF THESE LINES FAIL WITH BAD_EXC_ACCESS code=1
            translation.fromPhrase = pm.getOrCreatePhrase(fromPhrase)
            translation.toPhrase = pm.getOrCreatePhrase(toPhrase)
            //******

            if package != nil 
                package!.addTranslationObject(translation)
            

            return translation

        

    

创建短语对象:

func getOrCreatePhrase(data: NSDictionary) -> Phrase 
        // check if phrase exists
        let phraseId = data["id"] as! NSNumber

        if let phrase = self.getPhrase(phraseId) 

            return phrase

         else 

            let context = localDataStorage.context
            let lm = LanguageManager()
            let phrase = NSEntityDescription.insertNewObjectForEntityForName("Phrase", inManagedObjectContext: context) as! Phrase

            phrase.id = phraseId
            phrase.text = data["text"] as! String
            phrase.audioUrl = data["audio_url"] as? String

            let code = data["language"]!["language_code"] as! String
            phrase.language = lm.getLanguageFromCode(code)

            return phrase

        



    

调用 API:

func getPackageTranslations(package: Package, completion: ([Translation])-> Void) 

    let currentLanguage: Language = LanguageManager().getCurrentLanguage()!

    let urlString = baseAPIString + "/groups/\(package.id!)/translations/?language_code=\(currentLanguage.code)"


    let session = NSURLSession.sharedSession()
    let serachUrl = NSURL(string: urlString)


    let task = session.dataTaskWithURL(serachUrl!) 
        (data, response, error) -> Void in

        if error != nil 
            print(error?.localizedDescription)
         else 



            let jsonData: NSDictionary!
            do 
                jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary
             catch _ 
                jsonData = NSDictionary()
            

            let groupTranslationsData = jsonData["group_translations"] as! [NSDictionary]



            var translations = [Translation]()
            let context = LocalDataStorage().context

            for groupTranslation in groupTranslationsData 

                let translationData = groupTranslation["translation"] as! NSDictionary

                let translation = TranslationManager().getOrCreateTranslation(package, data: translationData)


                if translation.packages?.containsObject(package) == false 
                    //package.addTranslationObject(translation!)
                    //translation!.addPackageObject(package)
                

                translations.append(translation)

            


            do 
                try context.save()
             catch 
                print("There was a problem saving translation ")
            




            dispatch_async(dispatch_get_main_queue(), 
                completion(translations)
            )


        

    
    task.resume()


CoreData 上下文类:

    class LocalDataStorage 
    let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
    let context: NSManagedObjectContext!

    init() 
        context = appDelegate.managedObjectContext
    


【问题讨论】:

你在哪个线程上运行,上下文是如何配置的? 同意。很可能是由于此代码在创建 managedObjectContext 的不同线程上运行造成的。当你这样做时,它有时会起作用,有时会失败。 感谢两位的回复。 @Wain,我在上面的描述中添加了另外两个涉及的功能,以及正在运行的线程的图片。我确实调用了 API,但对象和 context.save 发生在调度之前。 【参考方案1】:

当您创建一个 NSManagedObjectContext 时,该上下文应与其交互的并发模式,并且您在与初始化期间指定的并发模式不同的线程上对其执行操作。

NSURLSession.dataTaskWithURL 的完成块是在另一个线程上运行的,所以你必须调度到上下文创建中指定的线程类型才能成功地对其执行任何操作。

如果您的上下文的并发类型是大多数情况下使用的 MainQueueConcurrencyType,您必须在主队列上执行上下文保存方法。

dispatch_async(dispatch_get_main_queue()) 
  do 
     try context.save()
   catch 
     print("There was a problem saving translation ")
  
  completion(translations)

【讨论】:

谢谢艾哈迈德!由于调度之前的其他步骤是同步的,我认为它们会在调度之前完全执行。在您的提示之后,我仍然遇到其他保存问题,最终将整个 JSON 解析和对象创建移动到调度块。现在就像一个魅力!

以上是关于创建新的 NSManagedObject 并将其分配给新的 NSManagedObject *有时*会失败的主要内容,如果未能解决你的问题,请参考以下文章

RestKit 0.20——创建新的 NSManagedObject 的首选方法是啥?

将现有的 NSManagedObject (s) 导入新的 .xcdatamodeld

为预览目的创建一个假的 NSManagedObjectContext?

创建NSManagedObject子类...在我的项目中创建一个新的错误

为 segue 中传递的新 nsmanagedobject 释放分配的上下文

如何在单个托管对象上下文中初始化新的 NSManagedObject 并设置与另一个 NSManagedObject 的关系?