需要一些帮助来了解Core Data中的瞬态属性

mor*_*tar 15 iphone core-data transient

我阅读有关瞬态属性的文档,但我无法真正理解它们的用途.如果我有这样的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
Run Code Online (Sandbox Code Playgroud)

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

我不是将网格声明为瞬态属性,一切都运行良好.我只是想知道如果我没有声明一个瞬态属性,是否会出现一些可能导致错误的异常情况.

我想如果你正在做这样的派生属性,我需要读取瞬态属性来获得正确的撤销行为.我没有使用撤销,无论如何我都看不出它在这种情况下是如何工作的.如果撤消了一个片段移动,则撤消管理器可以将_grid的旧值分配给它(可能假设我没有将其设置为只读),但旧值与新值相同.它是指向同一NSMutableArray实例的指针,只有内容已更改.无论如何我不使用撤销.

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

补充问题.如果我有这样的代码怎么办:

Board *board = someOtherManagedObject.board;
NSObject *boardContents = [[board.grid objectAtIndex:5] objectAtIndex:5];
Run Code Online (Sandbox Code Playgroud)

访问someOtherManagedObject.board后,是否可能出现故障?我也很难理解错误.我想在那种情况下我的代码会崩溃.我注意到唤醒将_grid设置为nil.我认为序列将是这样的:

  1. 网格吸气器叫
  2. _grid已分配
  3. self.pieces访问
  4. 故障火灾
  5. 叫醒了
  6. _grid = nil
  7. 回到网格吸气器
  8. [[_grid objectAtIndex:... 访问nil值,崩溃或至少没有操作
  9. grid getter返回nil
  10. 当boardContents包含值时,崩溃或不正确的行为

另一方面,也许如果我声明网格是一个瞬态属性,那么故障会在我的网格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
Run Code Online (Sandbox Code Playgroud)

如果我在故障上调用doSomething会发生什么?

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

瞬态属性解决了这个问题.transient属性提供了上下文无需保存即可观察的密钥.如果您遇到故障,向其发送切线属性的键值消息将触发上下文"触发"故障并加载完整的托管对象.

好的,但是如果我有一个不是属性访问器的实例方法,比如上面的doSomething呢?在我打电话之前,如何确保我有一个真实的物体?或者我可以调用它,并且方法体中的第一件事确保我有一个真实对象(例如通过访问建模属性)?

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

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

  1. 我甚至无法在故障上调用网格getter方法
  2. 我可以调用它,但我不能按照我想要的方式使用_grid

看来如果我理解你所说的并且确实如此,NSManagedObject的自定义子类非常有限.

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

如果是这种情况,那么您是不打算将应用程序逻辑放在自定义NSManagedObject子类中?应用程序逻辑是否应驻留在其他具有托管对象引用的类中,而托管对象只是您从中读取和写入的愚蠢对象(只是有点智能,具有维护数据一致性的一些功能)?将NSManagedObject子类化为非标准数据类型的一些"技巧"的唯一要点是什么?

Tec*_*Zen 43

瞬态属性的优势来自建模/观察属性与未建模/未观察属性之间的差异.

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

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

托管对象上下文不会观察未建模的属性,而未建模的属性需要自定义NSManagedObject子类.unmodeled属性仅是类的属性,不会显示在实体中,并且它们永远不会保留在Core Data中.对未建模属性的更改不会被上下文忽视.

错误是占位符对象,它们定义具有关系的对象图,但不加载属性值.你可以把它们想象成"幽灵"物体.它们将作为NSManagedObject或私有_NSFault ...类的实例进行记录.如果它是NSManagedObject,则属性都是空的.当故障"触发"或"故障"时,占位符对象将替换为可以读取其属性的完全填充的NSManagedObject实例.

因为未建模的属性只是自定义NSManagedObject子类的属性而不是实体,所以故障对象对它们一无所知.故障对象从数据模型初始化,以便它们响应的所有密钥必须位于数据模型中.这意味着故障不能可靠地响应对未建模属性的请求.

瞬态属性解决了这个问题.transient属性提供了上下文无需保存即可观察的密钥.如果您遇到故障,向其发送切线属性的键值消息将触发上下文"触发"故障并加载完整的托管对象.

请务必注意,尽管数据模型具有临时属性的键名,但该属性仅在完全实例化和加载受管对象时才具有值.这意味着当您执行任何仅在持久性存储中运行的提取时,切线属性将没有值.

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

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

您还可以使用:

+[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]
Run Code Online (Sandbox Code Playgroud)

...强制上下文查看未建模的属性.但是,如果未建模的属性具有副作用,则会导致意外和非管理行为.

我认为尽可能使用瞬态属性以确保一切都被覆盖是一种好习惯.]

更新:

好的,但是如果我有一个不是属性访问器的实例方法,比如上面的doSomething呢?在我打电话之前,如何确保我有一个真实的物体?

我认为你已经在考虑这个问题了,我的繁琐解释并没有任何帮助.

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
Run Code Online (Sandbox Code Playgroud)

Beta版:

@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
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个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;
//=>}) 
Run Code Online (Sandbox Code Playgroud)

注意:

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

这里的道德是:

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

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

如果您有疑问,只需自己对故障进行测试,以确保您的班级有效.