Core Data关联实体+异步请求:文章存储运行时报错求助
问题描述
我正在开发一款Feed应用,Core Data数据库中包含WebFeed与Article两个实体,二者为一对多关联。目前WebFeed的创建功能已实现,但编写高效的文章获取存储函数时遇到问题:该函数需在用户创建WebFeed后、ScrollView触发刷新时执行,从WebFeed保存的URL拉取文章并存入数据库,但执行let article = Article(context: taskcontext)时出现运行时错误,推测是未在正确队列执行,尝试多种方法仍未解决,寻求相关建议。
代码片段
WebFeedProvider 类
class WebFeedProvider { //MARK: - Logging let logger = Logger(subsystem: "com.danielw.WebFeeder", category: "persistence") //MARK: - CoreData static let shared = WebFeedProvider() /// A webFeeds provider for use with canvas previews. static let preview: WebFeedProvider = { let provider = WebFeedProvider(inMemory: true) WebFeed.makePreviews(context: provider.container.viewContext, count: 10) return provider }() private let inMemory: Bool private init(inMemory: Bool = false) { self.inMemory = inMemory } /// A persistent container to set up the Core Data stack. lazy var container: NSPersistentContainer = { let container = NSPersistentContainer(name: "WebFeeds") guard let description = container.persistentStoreDescriptions.first else { fatalError("Failed to retrieve a persistent store description.") } if inMemory { description.url = URL(fileURLWithPath: "/dev/null") } container.loadPersistentStores { storeDescription, error in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } } container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.name = "viewContext" container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return container }() /// Creates and configures a private queue context. private func newTaskContext() -> NSManagedObjectContext { /// Create a private queue context. let taskContext = container.newBackgroundContext() taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy /// Set unused undoManager to nil for macOS (it is nil by default on iOS) /// to reduce resource requirements. taskContext.undoManager = nil return taskContext } func fetchWebFeeds() async { await fetchArticles() } func importWebFeeds(from feedProperties: Set<FeedProperties>) { container.viewContext.perform { } } func createWebFeed(from urlString: String) async { logger.debug("Starting, creating a new WebFeed.") var parsedFeed = FeedProperties() do { try await WebFeedParser.shared.getFeed(from: urlString) { result in switch result { case .success(let feedProperties): parsedFeed = feedProperties case .failure(let error): print(error.rawValue) } } } catch { logger.error("\(error.localizedDescription)") } do { let taskContext = newTaskContext() try await taskContext.perform { let webFeed = WebFeed(context: taskContext) webFeed.id = UUID() webFeed.feedURL = parsedFeed.feedURL webFeed.homepage = parsedFeed.homePage webFeed.title = parsedFeed.title webFeed.summary = parsedFeed.summary try taskContext.save() } } catch { logger.error("\(error.localizedDescription)") } } private func fetchArticles() async { let taskContext = newTaskContext() await taskContext.perform { do { let feeds = try taskContext.fetch(WebFeed.fetchRequest()) for feed in feeds { guard let feedURL = feed.feedURL else { continue } let parser = WebFeedParser.shared Task { try await parser.fetchArticles(from: feedURL) { result in switch result { case .success(let articleProperties): for properties in articleProperties { let article = Article(context: taskContext) } case .failure(let parserError): self.logger.error("Failed to parse \(feedURL): \(parserError)") } } } } } catch { self.logger.error("Failed to fetch WebFeeds for Article Parsing: \(error.localizedDescription)") } } } /// Synchronously deletes given records in the Core Data store with the specified object IDs. func deleteWebFeeds(identifiedBy objectIDs: [NSManagedObjectID]) { let viewContext = container.viewContext logger.debug("Start deleting data from the store...") viewContext.performAndWait { objectIDs.forEach { objectID in let webFeed = viewContext.object(with: objectID) as! WebFeed viewContext.delete(webFeed) } } save() logger.debug("Successfully deleted data.") } /// Asynchronously deletes records in the Core Data store with the specified `WebFeed` managed objects. func deleteWebFeeds(_ webFeeds: [WebFeed]) async throws { let objectIDs = webFeeds.map { $0.objectID } let taskContext = newTaskContext() /// Add name and author to identify source of persistent history changes. taskContext.name = "deleteContext" taskContext.transactionAuthor = "deleteWebFeeds" logger.debug("Start deleting data from the store...") try await taskContext.perform { /// Execute the batch delete. let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs) guard let fetchResult = try? taskContext.execute(batchDeleteRequest), let batchDeleteResult = fetchResult as? NSBatchDeleteResult, let success = batchDeleteResult.result as? Bool, success else { self.logger.debug("Failed to execute batch delete request.") throw WebFeedError.batchDeleteError } } logger.debug("Successfully deleted data.") } private func save() { container.viewContext.perform { do { try self.container.viewContext.save() self.logger.debug("Successfully saved context.") } catch { self.logger.error("Failed to save context: \(error)") } } } }
WebFeedParser 类
class WebFeedParser { static let shared = WebFeedParser() private init() { } //MARK: - FeedCreation func getFeed(from urlString: String, completion: @escaping (Result<FeedProperties, ParserError>) -> Void) async throws { guard let url = URL(string: urlString) else { completion(.failure(.invalidURL)) return } var feedProperties = FeedProperties() let session = URLSession.shared var feedData: Data? do { let (data, _) = try await session.data(from: url) feedData = data } catch { completion(.failure(.connectionError)) } do { let decoder = SynDecoder() if let feedData { let decodedFeed = try decoder.decode(feedData) feedProperties.feedURL = url feedProperties.homePage = decodedFeed.siteURL feedProperties.title = decodedFeed.title feedProperties.summary = decodedFeed.summary completion(.success(feedProperties)) } } catch { completion(.failure(.decoderError)) } } func fetchArticles(from url: URL, completion: @escaping (Result<[ArticleProperties], ParserError>) -> Void) async throws { var articleProperties: [ArticleProperties] = [] let session = URLSession.shared var feedData: Data? do { let (data, _) = try await session.data(from: url) feedData = data } catch { completion(.failure(.connectionError)) } do { let decoder = SynDecoder() if let feedData { let decodedFeed = try decoder.decode(feedData) decodedFeed.children.forEach { feedProperties in var newArticle = ArticleProperties() newArticle.title = feedProperties.title newArticle.imageURL = feedProperties.imageURL newArticle.summary = feedProperties.summary newArticle.published = feedProperties.published newArticle.url = feedProperties.url articleProperties.append(newArticle) } completion(.success(articleProperties)) } } } }
内容的提问来源于stack exchange,提问作者Noem
相关产品推荐
相关产品推荐

