简体   繁体   English

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

[英]Need some help understanding transient properties in Core Data

I read the documentation on transient properties but I can't really understand their purpose.我阅读了关于瞬态属性的文档,但我无法真正理解它们的目的。 Can someone tell me the difference between having and not having a transient property if I have a custom subclass of NSManagedObject like this?如果我有一个像这样的 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

So pieces and grid present two ways to access the same data.所以块和网格提供了两种访问相同数据的方法。 pieces is the actual Core Data relationship property, and is a dense list of all the pieces.碎片是实际的核心数据关系属性,是所有碎片的密集列表。 grid is a way to find the contents of a particular space on the board addressed by (x, y) coordinates.网格是一种在由 (x, y) 坐标寻址的板上查找特定空间内容的方法。 grid is built lazily and updated (as long as it exists) when a piece changes location.当一块改变位置时,网格被懒惰地构建和更新(只要它存在)。

I'm not declaring grid as a transient property and everything is working fine.我没有将 grid 声明为瞬态属性,并且一切正常。 I'm just wondering if there is some unusual condition that could arise that would cause a bug if I don't declare a transient property.我只是想知道如果我不声明瞬态属性,是否会出现一些会导致错误的异常情况。

I think I read transient properties are needed to get proper undo behavior if you're doing a derived property like this.我想我读过瞬态属性需要获得正确的撤消行为,如果你正在做这样的派生属性。 I'm not using undo, and in any case I don't see how it could work in this case.我没有使用撤消,无论如何我不知道在这种情况下它是如何工作的。 If a piece move is undone, the undo manager can assign the old value of _grid back to it (maybe assuming I didn't make it readonly), but the old value is the same as the new value.如果一个动作被撤消,撤消管理器可以将 _grid 的旧值重新分配给它(可能假设我没有将其设置为只读),但旧值与新值相同。 It is a pointer to the same NSMutableArray instance, only the contents have changed.它是一个指向同一个 NSMutableArray 实例的指针,只是内容发生了变化。 Anyway I don't use undo.无论如何,我不使用撤消。

So do I get any benefit if I declare grid to be a transient property?那么,如果我将 grid 声明为一个临时属性,我会得到什么好处吗?

Additional question.补充问题。 What if I have code like this:如果我有这样的代码怎么办:

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

Is it possible board is a fault after accessing someOtherManagedObject.board?访问 someOtherManagedObject.board 后是否有可能是板子故障? I'm having trouble understanding faulting too.我也很难理解故障。 I think in that case my code would crash.我认为在那种情况下我的代码会崩溃。 I noticed awake sets _grid to nil.我注意到唤醒设置 _grid 为零。 I think the sequence would be like this:我认为顺序是这样的:

  1. grid getter called调用网格吸气剂
  2. _grid allocated _grid 已分配
  3. self.pieces accessed访问的自我片段
  4. fault fires故障火灾
  5. awake called醒着叫
  6. _grid = nil
  7. return to grid getter返回到网格吸气剂
  8. [[_grid objectAtIndex:... access nil value, crash or at least no-op [[_grid objectAtIndex:...访问 nil 值,崩溃或至少无操作
  9. grid getter returns nil网格 getter 返回 nil
  10. crash or incorrect behavior when boardContents is expected to contain a value当 boardContents 预期包含一个值时崩溃或不正确的行为

On the other hand, maybe if I declare grid to be a transient property, then the fault fires before my grid getter is called?另一方面,也许如果我将 grid 声明为一个瞬态属性,那么在调用我的 grid getter 之前会触发故障?

From TechZen:来自 TechZen:

Faults are placeholder objects that define an object graph with relationships but don't load attribute values.故障是占位符对象,用于定义具有关系的对象图但不加载属性值。 They will log as instances of either an NSManagedObject or of a private _NSFault... class.它们将记录为 NSManagedObject 或私有 _NSFault... 类的实例。

Because unmodeled properties are only attributes of the custom NSManagedObject subclass and not the entity, the fault objects know nothing about them.因为未建模的属性只是自定义 NSManagedObject 子类的属性而不是实体,所以故障对象对它们一无所知。 Fault objects are initialized from the data model so that all the keys they respond to must be in the data model.故障对象从数据模型初始化,因此它们响应的所有键都必须在数据模型中。 This means faults will not reliably respond to request for unmodeled properties.这意味着故障将无法可靠地响应对未建模属性的请求。

Wait what?等等什么? I'm starting to realize my objects can be faults at any time but are you telling me they might not even be instances of my class !?我开始意识到我的对象在任何时候都可能是错误的,但你是在告诉我它们甚至可能不是我的类的实例!? Or if you use a custom subclass are they guaranteed to be the sort of faults that are instances of NSManagedObject (specifically my subclass)?或者,如果您使用自定义子类,它们是否保证是 NSManagedObject 实例(特别是我的子类)的那种错误?

If they aren't instances of the custom class then what happens with something like this:如果它们不是自定义类的实例,那么会发生这样的事情:

@interface Foo : NSManagedObject {
    int data;
}

@property (nonatomic, retain) NSString *modeledProperty;

-(void)doSomething;

@end

@implementation Foo

@dynamic modeledProperty;

-(void)doSomething {
    data++;
}

@end

What happens if I call doSomething on a fault?如果我在错误上调用 doSomething 会发生什么?

  1. Doesn't respond to selector, crash不响应选择器,崩溃
  2. Runs my code, but my instance variables don't exist, who knows what happens when it does data++运行我的代码,但我的实例变量不存在,谁知道执行 data++ 时会发生什么
  3. data exists, just modeledProperty doesn't exist because it's a fault数据存在,只是modeledProperty不存在,因为它是一个错误

Transient properties fix this problem.瞬态属性解决了这个问题。 The transient property provides a key that the context can observe without saving.瞬态属性提供了上下文可以观察而不保存的键。 If you have a fault, sending it a key-value message for a transient property will trigger the context to "fire" the fault and load the complete managed object.如果您有错误,向它发送一个瞬态属性的键值消息将触发上下文“触发”错误并加载完整的托管对象。

Okay, but what if I have an instance method that's not a property accessor, like doSomething above?好的,但是如果我有一个不是属性访问器的实例方法,比如上面的 doSomething 呢? How do I make sure I have a real object before I call it?在调用之前,如何确保我有一个真实的对象? Or can I call it, and first thing in the method body make sure I have a real object (for example by accessing a modeled property)?或者我可以调用它,方法体中的第一件事是确保我有一个真实的对象(例如通过访问建模的属性)?

In your case, you want to use a transient property for grid if the value of grid depends on the values of any modeled properties of the Board class.在您的情况下,如果 grid 的值取决于 Board 类的任何建模属性的值,则您希望对 grid 使用瞬态属性。 That is the only way to guarantee that grid will always be populated when you access it.这是确保在您访问网格时始终填充网格的唯一方法。

I thought if it depended on the values of modeled properties, then it would fire the fault when it depended on them , ie the line for (PieceState *piece in self.pieces) fires the fault because it accesses self.pieces, which is a modeled property.我想如果它依赖于建模属性的值,那么它会在依赖它们时触发错误,即for (PieceState *piece in self.pieces)触发错误,因为它访问 self.pieces,这是一个模型属性。 But you are telling me which?但你告诉我哪个?

  1. I can't even call the grid getter method on a fault我什至不能在故障上调用网格 getter 方法
  2. I can call it but I can't use _grid the way I want to我可以调用它,但我不能按照我想要的方式使用 _grid

It seems if I understand what you're saying and it's true, the custom subclasses of NSManagedObject are very limited.看来如果我明白你在说什么并且是真的,NSManagedObject 的自定义子类非常有限。

  1. They can't have any instance methods that aren't modeled property getters or setters, because the object can't be guaranteed to exist in a useable state when they are called.它们不能有任何不是建模属性 getter 或 setter 的实例方法,因为在调用它们时不能保证对象处于可用状态。 (Exception: instance methods that are just helper methods for property accessors would be fine.) (例外:实例方法只是属性访问器的辅助方法也可以。)
  2. They can't have any instance variables for any useful purpose other than temporary caches of computed values, because those instance variables could be erased at any moment.除了计算值的临时缓存之外,它们不能有用于任何有用目的的任何实例变量,因为这些实例变量可能随时被删除。 I know they won't be persisted on disk, but I thought they would at least be persisted as long as I retained the object in memory.我知道它们不会保留在磁盘上,但我认为只要我将对象保留在内存中,它们至少会被保留。

If that's the case then are you not intended to put application logic in your custom NSManagedObject subclasses?如果是这种情况,那么您是否不打算将应用程序逻辑放在您的自定义 NSManagedObject 子类中? Should application logic reside in other classes that have references to the managed objects, and the managed objects are only dumb objects that you read from and write to (just a little bit smart, with some capabilities to maintain data consistency)?应用程序逻辑是否应该驻留在具有对托管对象的引用的其他类中,而托管对象只是您读取和写入的愚蠢对象(只是有点聪明,具有一些维护数据一致性的功能)? Is the only point of subclassing NSManagedObject to do some "tricks" with non-standard data types?子类化 NSManagedObject 的唯一要点是对非标准数据类型做一些“技巧”吗?

The advantage of transient properties comes from the difference between modeled/observed properties and unmodeled/unobserved properties.瞬态属性的优势来自建模/观察到的属性和未建模/未观察到的属性之间的差异。

The managed object context uses key-value observing (KVO) to monitor modeled properties.托管对象上下文使用键值观察 (KVO) 来监视建模属性。 Based on the information provided in the data model, it knows what properties must have values, what default, minimum and max values are, when the property is changed and, most importantly, whether the managed object has a key name for a property.根据数据模型中提供的信息,它知道哪些属性必须具有值,默认值、最小值和最大值是什么,属性何时更改,最重要的是,托管对象是否具有属性的键名。 All this provides the "managed" part of managed objects.所有这些都提供了托管对象的“托管”部分。

Modeled properties do not require a custom NSManagedObject subclass but can use a generic NSManagedObject instance initialized to an entity.建模属性不需要自定义 NSManagedObject 子类,但可以使用初始化为实体的通用 NSManagedObject 实例。 Accessing a modeled property of a fault (see below) causes the fault to load fully.访问故障的建模属性(见下文)会导致故障完全加载。

The managed object context doesn't observe unmodeled properties and unmodeled properties require a custom NSManagedObject subclass.托管对象上下文不观察未建模的属性,并且未建模的属性需要自定义 NSManagedObject 子类。 The unmodeled properties are attributes of the class only and do not show up in the entity and they are never persisted in Core Data.未建模的属性只是类的属性,不会出现在实体中,也不会持久存在于 Core Data 中。 Changes to unmodeled properties go unnoticed by the context.上下文不会注意到对未建模属性的更改。

Faults are placeholder objects that define an object graph with relationships but don't load attribute values.故障是占位符对象,用于定义具有关系的对象图但不加载属性值。 You can think of them as "ghost" objects.您可以将它们视为“幽灵”对象。 They will log as instances of either an NSManagedObject or of a private _NSFault... class.它们将记录为 NSManagedObject 或私有 _NSFault... 类的实例。 If it is a NSManagedObject the attributes are all empty.如果它是一个 NSManagedObject 属性都是空的。 When a fault "fires" or is "faulted in" the placeholder object is replaced with a fully populated NSManagedObject instance whose attributes can be read.当故障“触发”或“出现故障”时,占位符对象将替换为可以读取其属性的完全填充的 NSManagedObject 实例。

Because unmodeled properties are only attributes of the custom NSManagedObject subclass and not the entity, the fault objects know nothing about them.因为未建模的属性只是自定义 NSManagedObject 子类的属性而不是实体,所以故障对象对它们一无所知。 Fault objects are initialized from the data model so that all the keys they respond to must be in the data model.故障对象从数据模型初始化,因此它们响应的所有键都必须在数据模型中。 This means faults will not reliably respond to request for unmodeled properties.这意味着故障将无法可靠地响应对未建模属性的请求。

Transient properties fix this problem.瞬态属性解决了这个问题。 The transient property provides a key that the context can observe without saving.瞬态属性提供了上下文可以观察而不保存的键。 If you have a fault, sending it a key-value message for a transient property will trigger the context to "fire" the fault and load the complete managed object.如果您有错误,向它发送一个瞬态属性的键值消息将触发上下文“触发”错误并加载完整的托管对象。

It is important to note that although the data model has a key name for a transient property, the property only has a value when the managed object is fully instantiated and loaded.需要注意的是,虽然数据模型有一个瞬态属性的键名,但该属性只有在托管对象完全实例化和加载时才有值。 This means that when you do any fetches, which operate solely in the persistent store, the transient properties will have no values.这意味着当您执行任何仅在持久存储中操作的提取时,瞬态属性将没有值。

In your case, you want to use a transient property for grid if the value of grid depends on the values of any modeled properties of the Board class.在你的情况,你想用一个短暂的属性grid如果价值grid依赖于任何建模属性值Board类。 That is the only way to guarantee force Core Data to guarantee that grid will always be populated when you access it.这是 保证 强制 Core Data 保证在您访问grid时始终填充grid的唯一方法。

[Edit: That last is highly theoretical. [编辑:最后一个是高度理论化的。 Using a transient property ensures that Core Data tracks the property such that the accessing the property will cause a fault to fire and provide the data.使用瞬态属性可确保 Core Data 跟踪该属性,以便访问该属性将导致触发故障并提供数据。 However, in practice accessing any modeled property will reliably fire the fault and unmodeled methods are always available (see below.)然而,在实践中访问任何建模的属性将可靠地触发错误并且未建模的方法始终可用(见下文)。

You can also use:您还可以使用:

+[NSManagedObject contextShouldIgnoreUnmodeledPropertyChanges:]

… to force a context to watch unmodeled properties. ...强制上下文观察未建模的属性。 However, that can cause unanticipated and unmanaged behavior if the unmodeled properties have side effects.但是,如果未建模的属性具有副作用,则可能会导致意外和非托管行为。

I think that it is good practice to use transient properties whenever possible to make sure everything is covered.我认为最好尽可能使用瞬态属性来确保涵盖所有内容。 ] ]

Update:更新:

Okay, but what if I have an instance method that's not a property accessor, like doSomething above?好的,但是如果我有一个不是属性访问器的实例方法,比如上面的 doSomething 呢? How do I make sure I have a real object before I call it?在调用之前,如何确保我有一个真实的对象?

I think you're over thinking this and my cumbersome explanation didn't help any.我认为你想多了,我繁琐的解释没有任何帮助。

Core Data manages all these issues for you. Core Data 为您管理所有这些问题。 I've been using Core Data as long as there has been a Core Data and I have never run into any problems.只要有 Core Data,我就一直在使用 Core Data,而且我从未遇到过任何问题。 Core Data wouldn't be much use if you had to constantly stop and check if the objects were faults or not.如果您必须不断地停下来检查对象是否有问题,那么 Core Data 将没有多大用处。

For example, I set up a simple model with classes like so:例如,我用这样的类建立了一个简单的模型:

Alpha: Α:

@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

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

Then I created an object graph of Alpha object with a related Beta object.然后我创建了一个带有相关Beta对象的Alpha对象的对象图。 Then I restarted the app and ran a fetch of all Alpha objects.然后我重新启动了应用程序并运行了所有Alpha对象的提取。 Then I logged the following:然后我记录了以下内容:

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;
//=>}) 

Note that:注意:

  1. Both aa and bb are set to the expected class even though I did a generic object assignment.即使我进行了通用对象分配, aabb都设置为预期的类。 The context ensures that the fetch returns the proper class.上下文确保获取返回正确的类。
  2. Even bb 's class is Beta it reports as a fault meaning that the object represents an instance of the Beta class but that none of it's modeled properties are populated.即使bb的类是Beta它也会报告为错误,这意味着该对象表示Beta类的一个实例,但没有填充任何建模属性。
  3. The bb object responds to the unmodeledMethod selector even though within the method it still reports as a fault. bb对象响应unmodeledMethod选择器,即使在方法中它仍然报告为错误。
  4. Accessing the modeled property of Beta.num converts bb from a fault even before the call is made (the compiler sets it to trigger) but as soon as the access is done it reverts back to a fault.访问Beta.num的建模属性即使在调用之前(编译器将其设置为触发)也将bb从错误转换,但一旦访问完成,它就会恢复为错误。
  5. The objects in the relationships are not only faults but not the same objects returned by accessing the relationship.关系中的对象不仅是错误,而且不是通过访问关系返回的相同对象。 In Alpha.betas the Beta object has the address of 0x63454c0 whereas bb has the address of 0x639de70> while it is a fault.Alpha.betasBeta对象的地址为0x63454c0bb的地址为0x639de70>但它是一个错误。 After it converts from a fault and then back again, it's a address is 0x6029a80 .在它从故障转换然后再次返回之后,它的地址是0x6029a80 However, the managedObjectID of all three objects is the same.但是,所有三个对象的 managedObjectID 是相同的。

The morals here are:这里的道德是:

  • "faults" are more about the state of a managed object and less about the actual class. “故障”更多地与托管对象的状态有关,而与实际类无关。 Depending on how you access the object, you might get the actual subclass or you might get an instance of the hidden _NSFault… classes.根据您访问对象的方式,您可能会获得实际的子类,也可能会获得隐藏的_NSFault…类的实例。 From the coders perspective, all these different objects are interchangeable.从编码人员的角度来看,所有这些不同的对象都是可以互换的。
  • Even if a managed object reports as a fault, it will still respond to unmodeled selectors.即使托管对象报告为错误,它仍然会响应未建模的选择器。
  • Accessing any modeled property causes the fault to fire and the object becomes fully active.访问任何建模属性会导致故障触发,并且对象变得完全活动。
  • Core Data does a great deal of object swapping behind the scenes that you can't control and shouldn't worry about . Core Data 在幕后进行了大量您无法控制且不必担心的对象交换

In short don't worry about unmodeled properties and methods.总之不要担心未建模的属性和方法。 They should work transparently.他们应该透明地工作。 It's best practice to use transient properties especially if those properties have side effects with modeled properties.最好使用瞬态属性,尤其是当这些属性对建模属性有副作用时。 You can force a context to track unmodeled properties but that can cause unnecessary complexity.您可以强制上下文跟踪未建模的属性,但这可能会导致不必要的复杂性。

If you have doubts, just perform test yourself on faults to ensure that your class works.如果您有疑问,只需对错误进行自我测试,以确保您的课程有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM