首页 > 解决方案 > Swift CoreData - 未插入托管对象上下文的对象

问题描述

在进一步研究后,我重新研究了这个问题,并回应了它太长的评论。

我正在使用 CodableCSV 从三个 URL 下载和解码 CSV 格式的数据,并且我已经能够确认我收到了我期望的所有数据(截至今天,35027 行)。当数据被解码时,我将一个 NSManagedObjectContext 注入到解码的对象中。这是我的托管对象类:

import Foundation
import CoreData

@objc(MacListEntry)
class MacListEntry: NSManagedObject, Decodable {

  //var id = UUID()
  @NSManaged var registry: String?
  @NSManaged var assignment: String?
  @NSManaged var org_name: String?
  @NSManaged var org_addr: String?

  required convenience init(from decoder: Decoder) throws {

    guard let keyManObjContext = CodingUserInfoKey.managedObjectContext,
      let context = decoder.userInfo[keyManObjContext] as? NSManagedObjectContext,
      let entity = NSEntityDescription.entity(forEntityName: "MacListEntry", in: context) else {
        fatalError("Failed to receive managed object context")
    }

    self.init(entity: entity, insertInto: context)

    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.registry = try container.decode(String.self, forKey: .registry)
    self.assignment = try container.decode(String.self, forKey: .assignment)
    self.org_name = try container.decode(String.self, forKey: .org_name)
    self.org_addr = try container.decode(String.self, forKey: .org_addr)
  }

  private enum CodingKeys: Int, CodingKey {
    case registry = 0
    case assignment = 1
    case org_name = 2
    case org_addr = 3
  }
}

public extension CodingUserInfoKey {
  static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")
}

然后,我尝试使用以下方法保存上下文,try context.save()但在此之前,请检查我尝试使用以下方法插入的记录数:

print("Deleted objects: (self.persistentContainer.viewContext.deletedObjects.count)")
print("Inserted objects: (self.persistentContainer.viewContext.insertedObjects.count)")
print("Has changes: \(self.persistentContainer.viewContext.hasChanges)")

并在每次代码运行时获得不同数量的插入记录 - 总是很短,大约 0.5%。我很难理解在什么情况下以这种方式添加到托管对象上下文的对象不会出现在插入对象列表中,也不会出现在保存的数据库中。一次插入的记录数量是否有实际限制?

任何人都可以建议我应该在哪里寻找 - 错误很小,看起来程序运行良好,但事实并非如此。

非常感谢。

标签: swiftcore-datansurlrequestcombine

解决方案


我想我已经找到了问题,如果我是正确的,在这里发布我的解决方案可能会对其他人有所帮助。我正在使用 Combine 和 dataTaskPublisher 下载三个 CSV 文件 - 创建三个单独的发布者,然后将它们合并到一个流中。虽然我知道我无法在后台线程上更新 UI,所以在链中添加了 .receive(on: DispatchQueue.main) (在下面的代码中由 (1) 表示),我把它放在了 .receive(on: DispatchQueue.main) 之后。 tryMap { } 发生解码的地方。因为正是解码过程将对象插入到托管对象上下文中,所以这一定是异步发生的,因此会导致问题。通过将 .receive(on: ...) 行移动到 .tryMap 上方(参见下面的 (2)),这似乎解决了问题 - 或者至少进行了提取,

enum RequestError: Error {
    case sessionError(error: HTTPURLResponse)
}


struct Agent {

  let decoder: CSVDecoder

  struct Response<T> {
    let value: T
    let response: URLResponse
  }
  func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
    print("In run()")
    return URLSession.shared
      .dataTaskPublisher(for: request)
      .receive(on: DispatchQueue.main)    // -- (1)
      .tryMap { result -> Response<T> in
        print(result)

        guard let httpResponse = result.response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
          throw RequestError.sessionError(error: result.response as! HTTPURLResponse)
        }
        let value = try self.decoder.decode(T.self, from: result.data)
        return Response(value: value, response: result.response)
    }
    //.receive(on: DispatchQueue.main).    // -- (2)
    .eraseToAnyPublisher()
  }
}

struct Endpoint {
  var agent: Agent
  let base: String
}

extension Endpoint {

  func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<T, Error> {
    return agent.run(request)
      .map(\.value)
      .eraseToAnyPublisher()
  }

  func fetch(thing: String) -> AnyPublisher<[MacListEntry], Error> {
    return run(URLRequest(url: URL(string: base+thing)!))
  }

}

struct Response: Codable {
  let name: String
}

--- snip ---

    var requests:[AnyPublisher<[MacListEntry],Error>] = []
    requests.append(endpoint.fetch(thing: "file1.csv"))
    requests.append(endpoint.fetch(thing: "file2.csv"))
    requests.append(endpoint.fetch(thing: "file3.csv"))


    let _ = Publishers.MergeMany(requests)
      .sink(receiveCompletion: { completion in
        ...
      )
      .store(in: &bag)


推荐阅读