2017-05-30 51 views
0

我在獲取/創建我的用戶核心數據對象時遇到了奇怪的競態條件問題。我有兩種方法,handleGames()handelUsers(),它們在進行相應的網絡請求後被調用。核心數據同步創建和提取

用戶包含普通信息,如用戶名和ID。一個遊戲可以有許多用戶參與者。我們在覈心數據模型中創建了這種一對多關係。對於每個核心數據模型,我們都創建了擴展來處理提取和創建相應的核心數據對象。

import UIKit 
import CoreData 
import SwiftyJSON 

extension User { 
    func set(withJson json: JSON) { 
     self.id = json["id"].int32Value 
     self.username = json["username"].stringValue 
     self.email = json["email"].stringValue 
     self.money = json["money"].int32 ?? self.money 
     self.rankId = json["rank_id"].int32 ?? self.rankId 
     self.fullname = json["fullName"].stringValue 

     if let pictureUrl = json["picture"].string { 
      self.picture = json["picture"].stringValue 
     } 
     else { 
      let pictureJson = json["picture"] 
      self.pictureWidth = pictureJson["width"].int16Value 
      self.pictureHeight = pictureJson["height"].int16Value 
      self.picture = pictureJson["url"].stringValue 
     } 
    } 

    // Peform a fetch request on the main context for entities matching the predicate 
    class func fetchOnMainContext(withPredicate predicate: NSPredicate?) -> [User]? { 
     do { 
      let appDelegate = UIApplication.shared.delegate as! AppDelegate 
      let mainContext = appDelegate.persistentContainer.viewContext 
      let fetchRequest: NSFetchRequest<User> = User.fetchRequest() 
      fetchRequest.predicate = predicate 
      let objects = try mainContext.fetch(fetchRequest) 
      return objects 
     } catch { 
      print(error) 
     } 

     return nil 
    } 

    // Fetch a single entity that matches the predicate 
    class func fetch(id: Int32, context: NSManagedObjectContext) -> User? { 
     let predicate = NSPredicate(format: "id = %d", id) 
     return fetchAndPerformBlockIfNotFound({return nil}, withPredicate: predicate, inContext: context) 
    } 

    // Fetch a single entity that matches the predicate or create a new entity if it doesn't exist 
    class func fetchOrCreate(withPredicate predicate: NSPredicate?, inContext context: NSManagedObjectContext) -> User? { 
     return fetchAndPerformBlockIfNotFound({return User(context: context)}, withPredicate: predicate, inContext: context) 
    } 

    // Helper method for fetching an entity with a predicate 
    private class func fetchAndPerformBlockIfNotFound(_ block:() -> User?, withPredicate predicate: NSPredicate?, inContext context: NSManagedObjectContext) -> User? { 
     do { 
      let fetchRequest: NSFetchRequest<User> = User.fetchRequest() 
      fetchRequest.predicate = predicate 
      let objects = try context.fetch(fetchRequest) 

      if objects.count == 0 { 
       return block() 
      } else if objects.count == 1 { 
       return objects.first 
      } else { 
       print("ERROR: fetch request found more than 1 object") 
      } 
     } catch { 
      print(error) 
     } 

     return nil 
    } 
} 


import UIKit 
import CoreData 
import SwiftyJSON 

extension Game { 
    func set(withJson json: JSON) { 
     self.id = json["id"].int32Value 
     let pickerJson = json["picker"] 
     if let pickerId = pickerJson["id"].int32 { 
      self.pickerId = pickerId 
     } 
     self.pickerChoiceId = json["pickerChoice_id"].int32 ?? self.pickerChoiceId 
     self.creationDate = NSDate(timeIntervalSince1970: json["creationDate"].doubleValue) 
     if let completedTimeInterval = json["completedDate"].double { 
      self.completedDate = NSDate(timeIntervalSince1970: completedTimeInterval) 
     } 
     self.isPublic = json["isPublic"].boolValue 
     self.maturityRating = json["rating"].int32Value 
     self.beenUpvoted = json["beenUpvoted"].boolValue 
     self.beenFlagged = json["beenFlagged"].boolValue 
     let topCaptionJson = json["topCaption"] 
     if let before = topCaptionJson["fitbBefore"].string, 
      let after = topCaptionJson["fitbAfter"].string, 
      let userEntry = topCaptionJson["userEntry"].string { 
      self.topCaptionBefore = before 
      self.topCaptionAfter = after 
      self.topCaptionEntry = userEntry 
     } 
     if let picUrl = topCaptionJson["userPic"].string { 
      self.topCaptionUserPicUrl = picUrl; 
     } 

     let pictureJson = json["picture"] 
     self.pictureUrl = pictureJson["url"].stringValue 
     self.pictureWidth = pictureJson["width"].int16Value 
     self.pictureHeight = pictureJson["height"].int16Value 

     self.numUpvotes = json["numUpvotes"].int32Value 
     self.topCaptionUserId = topCaptionJson["userId"].int32 ?? topCaptionUserId 
     self.participants = NSSet() 
    } 
} 

下面是我們的handleGames()和handleUsers()方法在調用各自的網絡請求後調用的。這兩種方法都是由我們的NetworkManager異步調用的。在handleGames()中,我們還調用handleUsers()來設置每個遊戲的參與者。但是,當我們的NetworkManager同時爲其他任務同時調用handleUsers()時,我們會拋出多個對象被提取的錯誤,這意味着在提取之前已經創建了多個對象。我們嘗試使用performAndWait(),但仍然無法工作。

import CoreData 
import SwiftyJSON 

protocol CoreDataContextManager { 
    var persistentContainer: NSPersistentContainer { get } 
    func saveContext() 
} 

class CoreDataManager { 
    static let shared = CoreDataManager() 

    var contextManager: CoreDataContextManager! 

    private init() {} 

    // Perform changes to objects and then save to CoreData. 
    fileprivate func perform(block: (NSManagedObjectContext)->()) { 
     if self.contextManager == nil { 
      self.contextManager = UIApplication.shared.delegate as! AppDelegate 
     } 
     let mainContext = self.contextManager.persistentContainer.viewContext 

     block(mainContext) 

     self.contextManager.saveContext() 
    } 
} 

extension CoreDataManager: NetworkHandler { 
    func handleUsers(_ usersJson: JSON, completion: (([User])->())? = nil) { 
     var userCollection: [User] = [] 
     var idSet: Set<Int32> = Set() 
     self.perform { context in 

      for userJson in usersJson.arrayValue { 

       guard userJson["id"].int != nil else { 
        continue 
       } 
       let predicate = NSPredicate(format: "id = %@", userJson["id"].stringValue) 

       if !idSet.contains(userJson["id"].int32Value), let user = User.fetchOrCreate(withPredicate: predicate, inContext: context) { 
        user.set(withJson: userJson) 
        userCollection.append(user) 
        idSet.insert(userJson["id"].int32Value) 
        //Establish Relations 
        if let rankId = userJson["rank_id"].int32, let rank = Rank.fetch(id: rankId, context: context) { 
         user.rank = rank 
        } 
       } 
      } 
     } 
     completion?(userCollection) 
    } 

func handleGames(_ gamesJson: JSON, completion: (([Game])->())? = nil) { 
     var games: [Game] = [] 
     var idSet: Set<Int32> = Set() 
     self.perform { context in 

      for gameJson in gamesJson.arrayValue { 

       guard gameJson["id"].int != nil else { 
        continue 
       } 

       let predicate = NSPredicate(format: "id = %@", gameJson["id"].stringValue) 
       let gameId = gameJson["id"].int32Value 

       // Make sure there are no duplicates 
       if !idSet.contains(gameId), let game = Game.fetchOrCreate(withPredicate: predicate, inContext: context) { 
        game.set(withJson: gameJson) 
        games.append(game) 

        // Establish relationships 
        let userPredicate = NSPredicate(format: "id = %@", game.pickerId.description) 
        if let picker = User.fetch(id: game.pickerId, context: context) { 
         game.picker = picker 
        } 
        else if let picker = User.fetchOrCreate(withPredicate: userPredicate, inContext: context) { 
         picker.set(withJson: gameJson["picker"]) 
         game.picker = picker 
        } 
        if let pickerChoiceId = gameJson["pickerChoice_id"].int32, let pickerChoice = Caption.fetch(id: pickerChoiceId, context: context) { 
         game.pickerChoice = pickerChoice 
        } 
        idSet.insert(gameId) 

        // add participants to game 
        handleUsers(gameJson["users"]) { users in 
         print("handleUsers for game.id: \(game.id)") 
         for user in users { 
          print("user.id: \(user.id)") 
         } 
         game.participants = NSSet(array: users) 

        } 
       } 
      } 
     } 
     completion?(games) 
    } 
} 
+0

你在'handleUsers'和'handlerGames'函數的錯誤位置調用完成塊。您需要在完成塊內調用for循環後的'perform'。 – dan

+0

好的。我提出了完成調用,但問題仍然存在。 –

回答

0

我節省了核心數據速度不夠快時,其他電話試圖獲取用戶核心數據對象沒有發生的方式。我通過將self.contextManager.saveContext()perform()塊移到每個handleMethod()內解決了此問題。我認爲這不是絕對正確的做法,因爲仍然可能存在一些競爭條件,但對於一個小型項目來說,這是一個合適的解決方案。如果我稍後再找到答案,我會發布更好的答案。