You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5中Core Data向NSSet添加数据的问题(宝可梦场景)

Core Data Multi-Relation & Thread Safety Fixes

Let's break down your two Core Data issues and fix them step by step:

1. Pokemon Instance is Nil in fetchMoreDetails

Root Cause

The main problem here is Core Data thread safety violations:

  • You're passing a Pokemon instance from the main queue's view context to a background network thread in fetchMoreDetails. Core Data managed objects are bound to their context's queue—accessing them outside that queue causes them to become invalid (appear as nil or trigger faults).
  • There's also a potential context sync timing issue: after saving the private context in downloadPokemonsFromServer, the view context's fetched results controller might not immediately update, leading you to access stale objects.

Fix

Instead of passing the Pokemon instance directly, pass its thread-safe objectID. Then fetch the corresponding object in the private context of fetchMoreDetails, and always perform Core Data operations within the context's queue using perform:

Updated fetchMoreDetails Method

func fetchMoreDetails(pokemonObjectID: NSManagedObjectID, urlString: String, completion: @escaping ()->()) {
    guard let url = URL(string: urlString) else { 
        completion()
        return 
    }
    let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
    
    // Perform all Core Data work on the private context's queue
    privateContext.perform {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let err = error {
                print("Unable to get more details for pokemon", err)
                completion()
                return
            }
            guard let data = data else { 
                completion()
                return 
            }
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            do {
                let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
                // Fetch the Pokemon instance using the objectID in the private context
                guard let pokemon = privateContext.object(with: pokemonObjectID) as? Pokemon else {
                    print("Failed to fetch Pokemon from objectID")
                    completion()
                    return
                }
                
                pokemonDetailJSON.types.forEach { (nestedType) in
                    let type = Type(context: privateContext)
                    type.name = nestedType.type.name
                    // Add type to Pokemon's types relation (fix for issue 2)
                    let mutableTypes = pokemon.mutableSetValue(forKey: "types")
                    mutableTypes.add(type)
                }
                
                try privateContext.save()
                // Save parent context on its own queue
                privateContext.parent?.perform {
                    try? privateContext.parent?.save()
                    completion()
                }
            } catch let err {
                print("Unable to decode pokemon more details", err)
                completion()
            }
        }.resume()
    }
}

Updated handleRefresh in ViewController

@objc func handleRefresh() {
    print("DDDDD")
    Service.shared.downloadPokemonsFromServer {
        // Refresh fetched results to get latest data after save
        do {
            try self.pokemonController.performFetch()
        } catch let err {
            print("Failed to refetch pokemons: \(err)")
        }
        
        self.pokemonController.fetchedObjects?.forEach({ (pokemon) in
            print(pokemon.name)
            // Pass objectID instead of the managed object instance
            Service.shared.fetchMoreDetails(pokemonObjectID: pokemon.objectID, urlString: pokemon.url ?? "") {
                print(pokemon.abilities?.count)
            }
        })
        self.tableView.refreshControl?.endRefreshing()
    }
}

2. Can't Add Type to pokemon.types NSSet

Root Cause

NSSet is an immutable collection—calling pokemon.types?.adding(type) creates a new set but doesn't update the pokemon.types property. Additionally, you need to ensure the operation happens on the correct context's queue.

Fix

Use Core Data's built-in mutableSetValue(forKey:) method to get a mutable collection for the relation, then add your Type instance directly. This handles the Core Data relation management properly:

// Inside fetchMoreDetails, after creating the Type instance:
let mutableTypes = pokemon.mutableSetValue(forKey: "types")
mutableTypes.add(type)

This modifies the underlying relation directly, and changes will be saved when you persist the private context.

Additional Best Practices

  • Always wrap Core Data operations in context.perform { ... } when working with background contexts to ensure thread safety.
  • When saving a child context, save the parent context within its own queue using parent?.perform { ... } to avoid cross-thread issues.
  • For fetched results controllers, call performFetch() after context saves to ensure the UI reflects the latest data.

内容的提问来源于stack exchange,提问作者Luis Ramirez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 15:19:10