【问题标题】:Need some help understanding transient properties in Core Data需要一些帮助来理解 Core Data 中的瞬态属性
【发布时间】:2011-11-22 04:40:02
【问题描述】:

我阅读了有关瞬态属性的文档,但我无法真正理解它们的用途。如果我有这样的 NSManagedObject 的自定义子类,有人能告诉我有没有瞬态属性的区别吗?

@interface Board : NSManagedObject
{
    NSMutableArray *_grid;
}

// Core Data to-many relationship
@property (nonatomic, retain) NSSet *pieces;

@property (nonatomic, readonly) NSArray *grid;

-(void)awake;

-(void)movePiece:(PieceState *)piece to_x:(int)x y:(int)y;

@end


@implementation Board

@dynamic pieces;

-(void)awakeFromInsert {
    [super awakeFromInsert];
    [self awake];
}

-(void)awakeFromFetch {
    [super awakeFromFetch];
    [self awake];
}

-(void)awake {
    _grid = nil; // probably not necessary
}

-(NSArray *)grid {
    if (!_grid) {
        _grid = [[NSMutableArray alloc] initWithCapacity:10];
        for (int i = 0; i < 10; i++) {
            NSMutableArray *column = [[NSMutableArray alloc] initWithCapacity:10];
            [_grid addObject:column];
            for (int j = 0; j < 10; j++)
                [column addObject:[NSNull null]];
            [column release];
        }
        
        for (PieceState *piece in self.pieces)
            if (piece.x >= 0 && piece.y >= 0)
                [[_grid objectAtIndex:piece.x] replaceObjectAtIndex:piece.y withObject:piece];
    }
    
    return _grid;
}

-(void)movePiece:(PieceState *)piece to_x:(int)x y:(int)y {
    if (x >= 0 && y >= 0) {
        NSObject *capturedPieceObject = [[self.grid objectAtIndex:x] objectAtIndex:y];
        if ([capturedPieceObject isKindOfClass:[PieceState class]]) {
            PieceState *capturedPiece = (PieceState *)capturedPieceObject;
            [self removePiecesObject:capturedPiece];
            [[self managedObjectContext] deleteObject:capturedPiece];
            capturedPiece = nil;
        }
    }
    if (_grid) {
        if (piece.x >= 0 && piece.y >= 0)
            [[_grid objectAtIndex:piece.x] replaceObjectAtIndex:piece.y withObject:[NSNull null]];
        if (x >= 0 && y >= 0)
            [[_grid objectAtIndex:x] replaceObjectAtIndex:y withObject:piece];
    }
    
    [piece setX:x];
    [piece setY:y];
}
    
- (void)didTurnIntoFault {
    [_grid release];
    _grid = nil;
    
    [super didTurnIntoFault];
}

@end

因此,pieces 和 grid 提供了两种访问相同数据的方式。 pieces 是实际的 Core Data 关系属性,是所有片段的密集列表。网格是一种在 (x, y) 坐标寻址的板上查找特定空间内容的方法。当一块改变位置时,网格会延迟构建并更新(只要它存在)。

我没有将网格声明为瞬态属性,并且一切正常。我只是想知道如果我不声明瞬态属性,是否会出现一些异常情况导致错误。

如果您正在执行这样的派生属性,我认为我需要读取瞬态属性才能获得正确的撤消行为。我没有使用撤消,无论如何我看不出它在这种情况下如何工作。如果一块移动被撤消,撤消管理器可以将 _grid 的旧值分配回它(可能假设我没有将其设为只读),但旧值与新值相同。它是指向同一个 NSMutableArray 实例的指针,只是内容发生了变化。无论如何,我不使用撤消。

那么,如果我将网格声明为瞬态属性,我会得到什么好处吗?

附加问题。如果我有这样的代码怎么办:

Board *board = someOtherManagedObject.board;
NSObject *boardContents = [[board.grid objectAtIndex:5] objectAtIndex:5];

访问 someOtherManagedObject.board 后是否有可能是板故障?我也很难理解错误。我认为在那种情况下我的代码会崩溃。我注意到 awake 将 _grid 设置为 nil。我认为顺序应该是这样的:

  1. 调用网格吸气剂
  2. _grid 已分配
  3. self.pieces 已访问
  4. 故障引发
  5. 叫醒
  6. _grid = nil
  7. 返回网格获取器
  8. [[_grid objectAtIndex:... 访问 nil 值,崩溃或至少无操作
  9. grid getter 返回 nil
  10. 当 boardContents 应包含值时发生崩溃或错误行为

另一方面,如果我声明 grid 是一个瞬态属性,那么在调用我的 grid getter 之前故障会触发?

来自 TechZen:

故障是定义具有关系的对象图但不加载属性值的占位符对象。它们将作为 NSManagedObject 或私有 _NSFault... 类的实例进行记录。

因为未建模的属性只是自定义 NSManagedObject 子类的属性而不是实体,所以故障对象对它们一无所知。故障对象从数据模型初始化,因此它们响应的所有键都必须在数据模型中。这意味着故障将无法可靠地响应对未建模属性的请求。

等等什么?我开始意识到我的对象在任何时候都可能是错误的,但你是在告诉我它们甚至可能不是我的类的实例!?或者,如果您使用自定义子类,它们是否保证是 NSManagedObject 实例(特别是我的子类)的那种故障?

如果它们不是自定义类的实例,那么会发生这样的事情:

@interface Foo : NSManagedObject {
    int data;
}

@property (nonatomic, retain) NSString *modeledProperty;

-(void)doSomething;

@end

@implementation Foo

@dynamic modeledProperty;

-(void)doSomething {
    data++;
}

@end

如果我在出现故障时调用 doSomething 会发生什么?

  1. 不响应选择器,崩溃
  2. 运行我的代码,但是我的实例变量不存在,谁知道当它运行data++时会发生什么
  3. 数据存在,只是 modeledProperty 不存在,因为它是错误的

瞬态属性解决了这个问题。瞬态属性提供了上下文可以观察而无需保存的键。如果您有错误,向它发送一个瞬态属性的键值消息将触发上下文“触发”错误并加载完整的托管对象。

好的,但是如果我有一个不是属性访问器的实例方法,比如上面的 doSomething?在调用它之前如何确保我有一个真实的对象?或者我可以调用它吗,首先在方法主体中确保我有一个真实的对象(例如通过访问一个模型化的属性)?

在您的情况下,如果 grid 的值取决于 Board 类的任何建模属性的值,您希望为 grid 使用瞬态属性。这是保证在您访问网格时始终填充网格的唯一方法。

我认为如果它依赖于建模属性的值,那么它会触发错误当它依赖于它们时,即for (PieceState *piece in self.pieces) 行会触发错误,因为它访问了 self.pieces,这是一个模型属性。但你告诉我是哪个?

  1. 我什至不能在出现故障时调用网格 getter 方法
  2. 我可以调用它,但我不能以我想要的方式使用 _grid

如果我明白你在说什么,这是真的,NSManagedObject 的自定义子类似乎非常有限。

  1. 它们不能有任何不是建模属性 getter 或 setter 的实例方法,因为不能保证对象在调用时处于可用状态。 (例外:只是属性访问器的辅助方法的实例方法可以。)
  2. 除了计算值的临时缓存之外,它们不能有任何有用的实例变量,因为这些实例变量可能随时被删除。我知道它们不会保留在磁盘上,但我认为只要我将对象保留在内存中,它们至少会保留。

如果是这种情况,那么您不打算将应用程序逻辑放在您的自定义 NSManagedObject 子类中吗?应用程序逻辑是否应该驻留在对托管对象具有引用的其他类中,并且托管对象只是您读取和写入的哑对象(只是有点聪明,具有保持数据一致性的一些功能)?将 NSManagedObject 子类化以对非标准数据类型进行一些“技巧”是唯一的点吗?

【问题讨论】:

    标签: iphone core-data transient


    【解决方案1】:

    瞬态属性的优势在于建模/观察到的属性与未建模/未观察到的属性之间的差异。

    托管对象上下文使用键值观察 (KVO) 来监控建模属性。根据数据模型中提供的信息,它知道哪些属性必须具有值,默认值、最小值和最大值是什么,何时更改属性以及最重要的是,托管对象是否具有属性的键名。所有这些都提供了托管对象的“托管”部分。

    建模属性不需要自定义 NSManagedObject 子类,但可以使用初始化为实体的通用 NSManagedObject 实例。访问故障的建模属性(见下文)会导致故障完全加载。

    托管对象上下文不观察未建模属性,未建模属性需要自定义 NSManagedObject 子类。未建模的属性只是类的属性,不会出现在实体中,也不会保留在 Core Data 中。上下文不会注意到对未建模属性的更改。

    故障是定义具有关系的对象图但不加载属性值的占位符对象。您可以将它们视为“幽灵”对象。它们将作为 NSManagedObject 或私有 _NSFault... 类的实例进行记录。如果它是一个 NSManagedObject 属性都是空的。当故障“触发”或“出现故障”时,占位符对象将替换为完全填充的 NSManagedObject 实例,其属性可以读取。

    因为未建模的属性只是自定义 NSManagedObject 子类的属性而不是实体,所以故障对象对它们一无所知。故障对象从数据模型初始化,因此它们响应的所有键都必须在数据模型中。这意味着故障将无法可靠地响应对未建模属性的请求。

    瞬态属性解决了这个问题。瞬态属性提供了上下文可以观察而无需保存的键。如果您遇到错误,向其发送瞬态属性的键值消息将触发上下文“触发”错误并加载完整的托管对象。

    重要的是要注意,虽然数据模型有一个瞬态属性的键名,但该属性只有在托管对象完全实例化和加载时才有值。这意味着当您执行任何仅在持久存储中操作的提取时,瞬态属性将没有值。

    在您的情况下,如果grid 的值取决于Board 类的任何建模属性的值,您希望为grid 使用瞬态属性。这是保证强制Core Data保证grid在你访问它时总是被填充的唯一方法。

    [编辑: 最后一点是高度理论化的。使用瞬态属性可确保 Core Data 跟踪该属性,以便访问该属性将导致故障触发并提供数据。但是,在实践中,访问任何建模属性都会可靠地触发故障,并且未建模的方法始终可用(见下文。)

    你也可以使用:

    +[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]
    

    …强制上下文观察未建模的属性。但是,如果未建模的属性有副作用,这可能会导致意外和非托管行为。

    我认为最好尽可能使用瞬态属性来确保所有内容都被覆盖。]

    更新:

    好的,但是如果我有一个不是属性的实例方法怎么办 访问器,比如上面的 doSomething?我如何确保我有一个真实的 在我调用它之前对象?

    我认为你想太多了,我繁琐的解释没有任何帮助。

    Core Data 为您管理所有这些问题。只要有核心数据,我就一直在使用核心数据,而且我从未遇到任何问题。如果您必须经常停下来检查对象是否有故障,Core Data 就没有多大用处。

    例如,我用这样的类建立了一个简单的模型:

    阿尔法:

    @class Beta;
    
    @interface Alpha : NSManagedObject {
    @private
    }
    @property (nonatomic, retain) NSNumber * num;
    @property (nonatomic, retain) NSString * aString;
    @property (nonatomic, retain) NSSet *betas;
    
    -(NSString *) unmodeledMethod;
    @end
    
    @interface Alpha (CoreDataGeneratedAccessors)
    
    - (void)addBetasObject:(Beta *)value;
    - (void)removeBetasObject:(Beta *)value;
    - (void)addBetas:(NSSet *)values;
    - (void)removeBetas:(NSSet *)values;
    
    @end 
    
    @implementation Alpha
    @dynamic num;
    @dynamic aString;
    @dynamic betas;
    
    -(NSString *) unmodeledMethod{
      return @"Alpha class unmodeledMethod return value";
    }
    @end
    

    测试版:

    @class Alpha;
    
    @interface Beta : NSManagedObject {
    @private
    }
    @property (nonatomic, retain) NSNumber * num;
    @property (nonatomic, retain) NSSet *alphas;
    -(NSString *) unmodeledMethod;
    -(NSString *) accessModeledProperty;
    
    @end
    
    @interface Beta (CoreDataGeneratedAccessors)
    
    - (void)addAlphasObject:(Alpha *)value;
    - (void)removeAlphasObject:(Alpha *)value;
    - (void)addAlphas:(NSSet *)values;
    - (void)removeAlphas:(NSSet *)values;
    
    @end
    @implementation Beta
    @dynamic num;
    @dynamic alphas;
    
    -(NSString *) unmodeledMethod{
      return [NSString stringWithFormat:@"%@ isFault=%@", self, [self isFault] ? @"YES":@"NO"];
    }
    
    -(NSString *) accessModeledProperty{
      return [NSString stringWithFormat:@"\n isFault =%@ \n access numValue=%@ \n isFault=%@", [self isFault] ? @"YES":@"NO", self.num,[self isFault] ? @"YES":@"NO"];
      
    }
    @end
    

    然后我创建了一个 Alpha 对象的对象图和一个相关的 Beta 对象。然后我重新启动应用程序并获取所有Alpha 对象。然后我记录了以下内容:

    id aa=[fetchedObjects objectAtIndex:0];
    id bb=[[aa valueForKey:@"betas"] anyObject];
    
    NSLog(@"aa isFault= %@",[aa isFault] ? @"YES":@"NO");
    //=> aa isFault= NO
    
    NSLog(@"\naa = %@",aa);
    //=> aa = <Alpha: 0x63431b0> (entity: Alpha; id: 0x6342780 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Alpha/p1> ; data: {
    //=>  aString = "name 2";
    //=>  betas =     (
    //=>      "0x63454c0 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7>"
    //=>  );
    //=>  // ignore fetchedProperty = "<relationship fault: 0x6153300 'fetchedProperty'>";
    //=>  num = 0;
    //=> })
    
    NSLog(@"\nbb isFault= %@",[bb isFault] ? @"YES":@"NO");
    //=> bb isFault= YES
    
    NSLog(@"\nany beta = %@",[[bb  class] description]);
    //=> any beta = Beta
    
    NSLog(@"\n-[Beta unmodeledMethod] =\n \n %@",[bb unmodeledMethod]);
    //=> -[Beta unmodeledMethod] =
    //=>  <Beta: 0x639de70> (entity: Beta; id: 0x639dbf0 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7> ; ...
    //=>...data: <fault>) isFault=YES
    
    NSLog(@"\n-[Beta accessModeledProperty] = \n %@",[bb accessModeledProperty]);
    -[Beta accessModeledProperty] = 
    //=> isFault =NO 
    //=> access numValue=2 
    //=> isFault=YES
    
    NSLog(@"\nbb = %@",bb);
    //=>bb = <Beta: 0x6029a80> (entity: Beta; id: 0x6029460 <x-coredata://752A19D9-2177-45A9-9722-61A40973B1BC/Beta/p7> ; data: {
    //=>    alphas = "<relationship fault: 0x60290f0 'alphas'>";
    //=>    num = 2;
    //=>}) 
    

    注意:

    1. aabb 都设置为预期的类,即使我进行了通用对象分配。上下文确保提取返回正确的类。
    2. 即使 bb 的类是 Beta,它也会报告为错误,这意味着该对象表示 Beta 类的一个实例,但没有填充它的任何建模属性。
    3. bb 对象响应 unmodeledMethod 选择器,即使在方法中它仍然报告为错误。
    4. 访问 Beta.num 的建模属性甚至可以在调用之前将 bb 从错误中转换(编译器将其设置为触发),但一旦访问完成,它就会恢复为错误。
    5. 关系中的对象不仅是故障,而且与访问关系返回的对象不同。在Alpha.betas 中,Beta 对象的地址为0x63454c0,而bb 的地址为0x639de70&gt;,但这是一个故障。从故障转换回来后,它的地址是 0x6029a80。但是,所有三个对象的 managedObjectID 都是相同的。

    这里的道德是:

    • “故障”更多的是关于托管对象的状态,而不是实际类。根据您访问对象的方式,您可能会获得实际的子类,或者您可能会获得隐藏的_NSFault… 类的实例。从编码人员的角度来看,所有这些不同的对象都是可以互换的。
    • 即使托管对象报告为错误,它仍会响应未建模的选择器。
    • 访问任何建模属性会导致故障触发并且对象变为完全活动状态。
    • Core Data 会在幕后进行大量对象交换,您无法控制也不应该担心

    简而言之,不要担心未建模的属性和方法。他们应该透明地工作。最佳实践是使用瞬态属性,尤其是当这些属性对建模属性有副作用时。您可以强制上下文跟踪未建模的属性,但这可能会导致不必要的复杂性。

    如果您有疑问,只需对错误进行测试,以确保您的课程正常工作。

    【讨论】:

    • 我需要更多的说明,所以我问了一些额外的问题来回应你的回答。你能再看一遍问题吗?
    • 感谢您尝试测试。我认为您误解了#4中的结果。许多编译器首先评估函数的最后一个参数。它甚至可能在 C 标准中。我认为在评估最后一个参数时首先是错误,然后评估中间参数并触发错误,然后在评估第一个参数时它不是错误。
    • 是的,你可能是对的,但即便如此,我还是看到了在其他上下文中故障和完整对象之间的快速切换。
    • 我正在使用 propertiesToFetch 来预取我的瞬态依赖的属性。当我访问瞬态时, willAccessForKey:Transient 正在触发故障,即使它所依赖的数据已经预取到故障对象中。有什么办法可以防止这种情况发生,或者在这种情况下我应该切换到未建模的属性吗?谢谢
    猜你喜欢
    • 1970-01-01
    • 2012-02-17
    • 2013-09-10
    • 2012-02-10
    • 2012-02-02
    • 2016-02-10
    • 1970-01-01
    • 2022-09-24
    • 1970-01-01
    相关资源
    最近更新 更多