Core Data Transient Values with Swift

Core data transient values with Swift

Check mark the transient field in your data model for particular attribute(e.g. sectionTitle).

Create class for that entity, it will look something like

 class Message: NSManagedObject {

@NSManaged var body: String?
@NSManaged var time: NSDate?
@NSManaged var sectionTitle: String?
}

Edit it and make it like this:

class Message: NSManagedObject {

@NSManaged var body: String?
@NSManaged var time: NSDate?

var sectionTitle: String? {
return time!.getTimeStrWithDayPrecision()
//'getTimeStrWithDayPrecision' will convert timestamp to day
//just for e.g.
//you can do anything here as computational properties
}
}

Update- Swift4
Use @objc tag for Swift 4 as:

@objc var sectionTitle: String? {
return time!.getTimeStrWithDayPrecision()
}

Semantics of optional in the context of Transient Attributes in CoreData

If I don't set the transient attribute to optional I get the following error:
Core Data Save Error (NSValidationErrorKey, Cocoa error 1570) which according to the following SO Q/A is an error you get when you try and store an entity with non-optional attributes set to Null.

If I set the attribute to optional this error does not occur. So it seems as though you need to set transient attributes to optional to let core data know that you don't need/want to store a value for the transient attribute

Need some help understanding transient properties in Core Data

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. 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. 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. 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. 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. If it is a NSManagedObject the attributes are all empty. When a fault "fires" or is "faulted in" the placeholder object is replaced with a fully populated NSManagedObject instance whose attributes can be read.

Because unmodeled properties are only attributes of the custom NSManagedObject subclass and not the entity, the fault objects know nothing about them. 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. That is the only way to guarantee force Core Data to guarantee that grid will always be populated when you access it.

[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. 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? 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. 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 wouldn't be much use if you had to constantly stop and check if the objects were faults or not.

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. Then I restarted the app and ran a fetch of all Alpha objects. 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. 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.
  3. The bb object responds to the unmodeledMethod selector even though within the method it still reports as a fault.
  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.
  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. After it converts from a fault and then back again, it's a address is 0x6029a80. However, the managedObjectID of all three objects is the same.

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. 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.

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.

How do iOS Core Data Transients Work?

maxpower,

Transients are quite simple. They are properties that are always non-existent in the backing store. Hence, the fact that you ever see them is because you are using a child MOC and have externally assigned those values. To ensure that a transient is always valid, you need to consider implementing the -awakeFromInsert, -awakeFromFetch, -prepareForDeletion, -didTurnIntoFault and -willTurnIntoFault methods.

Andrew

Difference between transient and derived properties of a core data entity

According to Apple's guide for Non-Standard Persistent Attributes:

You can use non-standard types for persistent attributes either by using transformable attributes or by using a transient property to represent the non-standard attribute backed by a supported persistent property. The principle behind the two approaches is the same: you present to consumers of your entity an attribute of the type you want, and “behind the scenes” it’s converted into a type that Core Data can manage. The difference between the approaches is that with transformable attributes you specify just one attribute and the conversion is handled automatically. In contrast, with transient properties you specify two attributes and you have to write code to perform the conversion.

I suggest using transient attributes. Idea is that you create 2 string attributes: countryName (non-transient) and localizedCountryName (transient):

How to set "transient" flag

And then, in your NSManagedObjectSubclass, you simply implement a getter for localizedCountryName:

- (NSString *)localizedCountryName
{
NSString *result;

if ([self.countryName length] > 0) {
result = NSLocalizedString(self.countryName, nil);
}

return result;
}

iOS - How to refresh/update Core Data transient property?

Transient properties are refreshed when you send a refreshObject:mergeChanges: to your object.

The solution provided by Mundi to apply key value observing mechanism might work too and if it does, it is definitely much more convenient than refreshing explicitly.



Related Topics



Leave a reply



Submit