【问题标题】:Core-Data willSave: method核心数据 willSave: 方法
【发布时间】:2011-06-19 22:33:12
【问题描述】:

我的Entity A. 中有一个属性modificationDate,我想在保存NSManagedObject 时设置它的值。但是,如果我尝试在 NSManagedObject willSave: 方法中执行此操作,则会出现错误:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to process pending changes before save.  The context is still dirty after 100 attempts.  Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.' ***

那么,我想知道,设置modificationDate 值的最佳方法是什么?

【问题讨论】:

    标签: objective-c core-data nsmanagedobject


    【解决方案1】:

    Swift 4 解决方案,它结合了 zmit 和 Richard 的答案,无需重复到 NSNotification

    override func willSave() {
        let expectedNewValue = "Your new value"
        if customField != expectedNewValue, changedValues()[#keyPath(Entity.customField)] == nil, !isDeleted {
            customField = expectedNewValue
        }
        super.willSave()
    }
    

    【讨论】:

      【解决方案2】:

      显然已经有几个很好的解决方案可以解决这个问题,但我想提出一个最适合我遇到的特定场景的新解决方案。

      (在 Swift 中:)

      override func willSave() {
          if self.changedValues()["modificationDate"] == nil {
              self.modificationDate = NSDate()
          }
      
          super.willSave()
      }
      

      我需要这个的原因是因为我有一个特殊的需要有时需要手动设置 modifyDate。 (有时我手动设置时间戳的原因是因为我试图使其与服务器上的时间戳保持同步。)

      这个解决方案:

      1. 防止 willSave() 无限循环,因为一旦设置了时间戳,它就会出现在 changedValues() 中
      2. 不需要使用观察
      3. 允许手动设置时间戳

      【讨论】:

      • 有趣。我们试图避免对服务器上的任何关键内容使用任何客户端时间戳。设备时钟不可信,可能会导致同步算法出现乱序错误。 (通常在客户端存储一个“lastSynced”值并使用它来仅向服务器询问那些已更改的对象)。
      • @Sam,我将它用于编辑它的用户具有 authority 来解决合并冲突的数据。虽然我没有提供用于解决的 UI,所以我只是使用时间戳来自动解决冲突。由于用户对该数据拥有权限,因此我不会担心他们的时钟可能是错误的。它可能会打扰我的用户,但他们也可能会因为我错过他们的约会而感到困扰:)。但你的观点是正确的,我认为我不会将这种方法用于公共数据。
      【解决方案3】:

      实际上,比接受的答案更好的方法是使用原始访问器,正如NSManagedObject's Documentation 中所建议的那样

      `

      - (void)willSave
      {
          if (![self isDeleted])
          {
              [self setPrimitiveValue:[NSDate date] forKey:@"updatedAt"];
          }
          [super willSave];
      }
      

      `

      另外,检查对象是否用-isDeleted 标记为删除,因为-willSave 也会被调用。

      【讨论】:

      • 文档提到了关于覆盖 willSave 的内容:“如果您使用原始访问器更改属性值,您可以避免无限递归的可能性,但 Core Data 不会注意到您所做的更改。”
      • @PietroRea 它会被保存到数据库吗?
      【解决方案4】:

      事实上apple docs(在接受的答案中只读了一半)不推荐这种方法。他们明确说你应该使用 NSManagedObjectContextWillSaveNotification。一个例子可能是:

      @interface TrackedEntity : NSManagedObject
      @property (nonatomic, retain) NSDate* lastModified;
      @end
      
      @implementation TrackedEntity
      @dynamic lastModified;
      
      + (void) load {
          @autoreleasepool {
             [[NSNotificationCenter defaultCenter] addObserver: (id)[self class]
                                                      selector: @selector(objectContextWillSave:)
                                                          name: NSManagedObjectContextWillSaveNotification
                                                        object: nil];
          }
      }
      
      + (void) objectContextWillSave: (NSNotification*) notification {
         NSManagedObjectContext* context = [notification object];
         NSSet* allModified = [context.insertedObjects setByAddingObjectsFromSet: context.updatedObjects];
         NSPredicate* predicate = [NSPredicate predicateWithFormat: @"self isKindOfClass: %@", [self class]];
         NSSet* modifiable = [allModified filteredSetUsingPredicate: predicate];
         [modifiable makeObjectsPerformSelector: @selector(setLastModified:) withObject: [NSDate date]];
      }
      @end
      

      我使用它(与其他一些方法:例如主键)作为大多数核心数据项目的抽象基类。

      【讨论】:

      • 我喜欢!只是在寻找这样的东西-欣赏示例代码。我需要调用 [load super] 吗? (如果 NSManagedObject 或其父级对它不做任何事情,则怀疑否。)
      • 好的!我试过这个,它似乎太热心了(或者更有可能我在滥用它)。我有一个使用 lastModified 的托管对象(通过 MYAPPTrackedManagedObject,基本上是上面的 TrackedEntity)。但是,此对象包含与其他使用 lastModified 的对象的关系。因此, setLastModified: 将不会被其他对象识别,并引发异常。也许我需要以某种方式拨回一点?
      • 啊,也许只是遍历 allModified 并过滤掉任何不响应 setLastModified: 的内容,然后使用它。
      • +load 在运行时加载类时调用 - 即:保证在进行上下文更新之前调用。 +initialize 在第一次访问类时调用。为了确保捕获所有通知,我使用了 +load。
      • load 在类被添加到 ObjC 运行时时被调用。这通常位于 main.m 文件中的全封闭 @autoreleasepool 之前。如果您没有设置自动释放池,对象将会泄漏。
      【解决方案5】:

      来自 willSave 的 NSManagedObject 文档:

      如果要更新持久属性值,通常应在进行更改之前测试任何新值与现有值是否相等。如果您使用标准访问器方法更改属性值,Core Data 将观察生成的更改通知,因此在保存对象的托管对象上下文之前再次调用 willSave。如果你继续修改 willSave 中的值,willSave 将继续被调用,直到你的程序崩溃。

      例如,如果您设置了最后修改的时间戳,您应该检查您之前是否在相同的保存操作中设置了它,或者现有的时间戳不小于当前时间的一个小增量。通常,最好为所有正在保存的对象计算一次时间戳(例如,响应 NSManagedObjectContextWillSaveNotification)。

      所以也许是这样的:

      -(void)willSave {
          NSDate *now = [NSDate date];
          if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
              self.modificationDate = now;
          }
      }
      

      您可以在哪里调整 1.0 以反映预期保存请求之间的最小差异。

      【讨论】:

      • 您还需要检查 modifyDate 是否在 changedValues 中
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多