Handling undefined keys in Core Data - ios

Is it possible to set a handler for undefined keys in Core Data?
I'm asking because despite defining valueForUndefinedKey: my implementation of that method is never called if valueForKey: is invoked on a managed object that doesn't have an attribute with that key.
This is needed for a synchronization system I'm currently writing where an object can be marked as locallyCreated or locallyDeleted but at the same time not all objects are editable so I want to avoid defining these properties for all entities in my model (around ~25 entities).
Although it seems tempting to create a single parent entity for that purpose I would like to avoid doing that since that will put all objects in one giant SQLite table which as far as I know will have negative impact on performance.
Currently I have a base "entity" class called RemoteObject that defines some common attributes like remoteID, locallyCreated, locallyDeleted, as suggested in another answer on SO, which all other entities inherit in code like this:
#interface RemoteObject : NSManagedObject
#property (nonatomic) NSString *remoteID;
#property (nonatomic) BOOL locallyCreated;
#property (nonatomic) BOOL locallyDeleted;
#end
#implementation RemoteObject
#dynamic remoteID;
#dynamic locallyCreated;
#dynamic locallyDeleted;
#end
#interface Project : RemoteObject
// custom properties
#end
What I want is to inspect any given RemoteObjet and see if it was locally create or deleted. However, as I said above, not all of the entities have corresponding attributes, so Core Data will throw an exception.
I found a workaround that allows me to avoid those errors - define a class method instead:
- (id)valueForKeyIfExists:(NSString *)key {
if (self.entity.attributesByName[key] != nil) {
return [self valueForKey:key];
}
return nil;
}
+ (BOOL)objectIsLocallyCreated:(RemoteObject *)object {
return [[object valueForKeyIfExists:#"locallyCreated"] boolValue];
}
But I was wondering if it would be possible to refactor this into object properties instead, catching undefined keys with valueForUndefinedKey: like this:
- (id)valueForUndefinedKey:(NSString *)key {
if ([key isEqualToString:LocallyCreatedKey]
|| [key isEqualToString:LocallyDeletedKey]) {
return #(NO);
}
return [super valueForUndefinedKey:key];
}
- (BOOL)locallyDeleted {
return [[self valueForKey:LocallyDeletedKey] boolValue];
}

It would be better to move those 2 flag attributes into a different class and then anything which doesn't have them is a subclass of RemoteObject and anything which does is a subclass of the new class, perhaps TrackedRemoteObject. Then in your algorithm you can class test to determine conformance.

Related

Why NSManagedObject Subclass can't use class_copyMethodList find dynamic generated method?

When I create a NSManagedObject Subclass Employee,it has a property nameaccording the EntityDescription in xcdatamodelfile. And in the .m file, the code modify it using #dynamic like this:
#interface Employee (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *name;
#end
#implementation Employee (CoreDataProperties)
#dynamic name;
#end
According to the Apple's Document:
Core Data dynamically generates efficient public and primitive get and set attribute accessor methods and relationship accessor methods for properties that are defined in the entity of a managed object’s corresponding managed object model. Therefore, you typically don’t need to write custom accessor methods for modeled properties.
According this, I think the CoreData Framework will create two method named name and setName:in the runtime. So I use such code to verify my thinking.
Employee *object = [NSEntityDescription insertNewObjectForEntityForName:#"Employee" inManagedObjectContext:self.managedObjectContext];
object.name = #"1";
[[self class] showInstanceMethod:[Employee class]];
+ (void)showInstanceMethod:(Class)class {
unsigned int outCount;
//..show InstanceMethodList
Method *methodsList = class_copyMethodList(class, &outCount);
for (int i = 0; i < outCount; i ++) {
SEL sel = method_getName(*methodsList);
NSString *methodName = NSStringFromSelector(sel);
NSLog(#"\nmethodName:%#\n", methodName);
methodsList++;
}
}
I'm sad it didn't log any method name like name or setName:.But I use this code object.name = #"1"; and didn't have any problem.
When they say "dynamically" they really do mean it - the dynamic implementation seems to be provided only as and when the selector is called (directly or via valueForKey:). You can see this happening if you override resolveInstanceMethod: in your Employee class. Call the super implementation and log the selector name and return value. Presumably the method will be listed by class_copyMethodList at this point, though I've never checked.

How to run getter methods when adding objects to array?

Basically I'm trying to implement a getter and a setter for my mutable array.
The getter gets called fine, however the setter is apparently only called when I directly set the array equal to something (using =).
However the setter is not called when I add an item to the array using the code below:
[self.HighScores insertObject:newScore atIndex:i];
I see that there is a bunch of extra methods being "suggested" by xcode such as:
-(void) insertObject:(NSObject *)object inHighScoresAtIndex:(NSInteger)index
However having added it it still doesn't get called.'
Parts of my code are listed below:
HighScoreCollection.h:
#interface HighScoreCollection : NSObject
#property(nonatomic) NSMutableArray *HighScores;
- (bool) AddHighScore: (HighScore* )newScore;
- (NSMutableArray*) HighScores;
- (void) setHighScores:(NSMutableArray*)HighScores;
#end
HighScoreCollection.m:
#implementation HighScoreCollection
#synthesize HighScores = _HighScores;
- (void) setHighScores:(NSMutableArray*)HighScores
{
//setter code
}
- (NSMutableArray*) HighScores
{
//getter code
}
#end
How do I run a setter when I call array methods such as insert object etc. ?
Add a class
Inherit from NSObject
Inside the object from step 2 own a NSMutableArray in this custom object (composition design pattern link).
After that, call your custom setter in your new composite object when u want to.
Note: Your design is wrong, and you shouldn't thinks how the NSMutableArray insert object implement.

why isn't lazy instantiation used in every getter

In the stanford course Paul Hegarty prefers to use lazy instantiation. For instance he makes a private declaration of
#property (strong, nonatomic) (NSArray *)cards
and then he uses the getter to perform an initialization
- (NSArray *) cards
{
if(!_cards) _cards = [[NSArray alloc]init]
return _cards;
}
I'm cool with that. What I don't get though is that at another time Paul declares a public suit for a playingCard being:
#property (strong, nonatomic) NSString *suit;
but in the implementation he doesn't perform this lazy instantiation. So I don't understand where the alloc init of the suit string happens? (suit being a pointer to an NSString - object which ought to get a place in the heap)
#import "PlayingCard.h"
#implementation PlayingCard
#synthesize suit = _suit;
- (void)setSuit:(NSString *)suit
{
if ([#[#"♣︎", #"♠︎", #"♥︎", #"♦︎"]containsObject: suit]) {
_suit = suit;
}
}
- (NSString *)suit
{
return _suit? _suit: #"?";
}
#end
The property is public, so he assumes that it will be set somewhere. When you set this property you can alloc, init and then set it to Playing card instance, for example:
PlayingCard *playingCard = [PlayingCard new];
[playingCard setSuit:#"spade"];
Lazy initialisation is used if property is private (so you can not initialise it outside of the class), but you don't want to initialise it in init method of the class.
When you ask for the cards instance there is no additional information required (i.e. there are no necessary parameters). You just instantiate a PlayingCard and return it.
A suit, on the other hand, could be one of four options, so somebody needs to set that somewhere. Note that this issue is really independent of lazy initialization. It has more to do with the fact that suit expects to be initialized with a user-parameterized value.
Lazy initialization is a way to say, "don't bother creating an instance of this object until I ask for it." But in the case of suit, you don't want to create the string until the user supplies it.
Lazy instantiation is not a panacea but can improve the object instantiation and app responsiveness if you don't spend cycles instantiating ivar objects all at once before you need them. This effect is nothing on a small simple class, but if you have a large set or array of objects, setting them all up completely at once will slow down things at one point.
To be fair the same hit could come later.

Objective-C properties and instance variables

Problem
I want to create an interface with this signature, but without auto-synthesized instance variables:
#interface MyObject : NSObject
#property (nonatomic, copy) NSArray *values;
#end
Question:
Is it possible to prevent instance variable to be auto synthesized in .m #implementaion, as I want to implement my own getter and setter and I'm not going to use instance variable.
Reason:
The reason is that I don't want to have memory overhead, as data is going to be stored in plain bytes archive. At the same time I don't want users to know implementation issues and keep interface signature unchanged.
#implementation MyObject {
NSData *_data
{
- (NSArray *)values
{
// Generate NSArray from _data
}
- (void)setValues(NSArray *)values
{
// Set _data from values
}
#pragma mark - Hidden init
- (id)initWithData:(NSData *)data
{
// Set _data
}
#end
If you implement both getter and setter yourself, instance variables are not synthesized.
As others said - if you override the setter and getter - the compiler does't do anything else. So what you want.. is what you have typed out.
If you dont wantbto create just create only instance variable.
#interface MyObject : NSObjet
{
NSArray *values;
}
#end

CoreData transient relationship example

Does anybody have an example on how to model and code a transient to-one relationship in CoreData? For example, I have 2 entities with a one-to-many relationship. Doctor and Appointment. Now I want an transient relationship called mostRecentAppointment on the doctor entity. It's straightforward to model in the xcode designer, but I'm not sure about the implementation side. Also should I implement an inverse? Seems silly.
Have a look at this code I wrote recently, to cache an image in an NSManagedObject:
First you define a transient property in your model (notice that if your transient property points to an object type other than those supported by CoreData you'll leave as "Undefined" in the model)
Then, you re-generate your NSManagedObject subclass for that entity or just add the new property manually, the header file should look like this:
#interface Card : NSManagedObject
#property (nonatomic, retain) NSString * imagePath;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSNumber * order;
#property (nonatomic, retain) NSString * displayName;
#property (nonatomic, retain) UIImage *displayImage;
#end
Here we change the class of the transient property to the actual class type
e.g. displayImage type here is UIImage.
In the implementation file (or an extension class) you implement the getter/setter for your transient property:
-(UIImage*)displayImage{
//Get Value
[self willAccessValueForKey:#"displayImage"];
UIImage *img = (UIImage*)[self primitiveValueForKey:#"displayImage"];
[self didAccessValueForKey:#"displayImage"];
if (img == nil) {
if ([self imagePath]) { //That is a non-transient property on the object
img = [UIImage imageWithContentsOfFile:self.imagePath];
//Set Value
[self setPrimitiveValue:img forKey:#"displayImage"];
}
}
return img;
}
Hope that helps you.
What you need to do is add an entity of type Appointment called newAppointment and set this each time you create a new appointment for a given doctor. Its that simple.
Always implement an inverse as apple recommend this for validation and core data efficiency.
Alternatively you could timestamp the appointments and use NSPredicates to search for the latest appointment in a given Doctor's linked appointments.
In this case, the appropriate method to override is -awakeFromFetch in the Doctor entity, for example like so:
- (void)awakeFromFetch {
[super awakeFromFetch];// important: call this first!
self.mostRecentAppointment = <something>; // normal relationship
self.mostRecentAppointment.doctor = self; // inverse relationship
}
In the model designer, mark both the normal and the inverse relationship as transient. That should be it.
Well, you'll just have to try out, in your own sample program that can be no more than an hour to set up correctly.
My guess is --- no extra coding will be needed. If Apple's documentation on CoreData is correct, the only difference between a normal attribute/relationship and a "transient" one is that the latter is not persisted, meaning, when you "save" it does not update the persistent-store.
I would guess that otherwise all the aspects of it are complete, together with KVO/KVC compliance, Undo support, validation, and automatic update by delete rules. The only thing is that after a fresh Fetch of the entity --- the transient relationship will always be nil.
For that --- I would of course NOT RECOMMEND setting up a transient relationship as "non-optional", because it is very likely to be null most of the time for most of the entities.
I would set up a reverse relationship (transient as well and named wisely) and have both delete rules be "Nullify".
So far is for transient relation.
But here is an alternative I came up with, trying to solve almost-the-same problem. My "appointment" is one of the related appointments, but not just the "latest", but the first "unfinished" one. Very similar logic.
Instead of a transient relationship, I added a new calculated property to my "Doctor" entitys generated NSManagedObject subclass, in a category, like this:
#interface XXDoctor (XXExtensions)
/**
#brief Needs manual KVO triggering as it is dependent on a collection.
Alternatively, you can observe insertions and deletions of the appointments, and trigger KVO on this propertyOtherwise it can be auto-
#return the latest of the to-many appointments relation.
**/
#property (readonly) XXAppointment *latestAppointment; // defined as the
#end
Implementation:
#import "XXDoctor".h"
#import "XXAppointment.h"
#implementation XXDoctor (XXExtensions)
// this won't work because "appointments" is a to-many relation.
//+ (NSSet *)keyPathsForValuesAffectingLatestAppointment {
// return [NSSet setWithObjects:#"appointments", nil];
//}
- (XXAppointment *) latestAppointment {
NSInteger latestAppointmentIndex = [self.appointments indexOfObjectPassingTest:^BOOL(XXAppointment *appointment, NSUInteger idx, BOOL *stop) {
*stop = (appointment.dateFinished == nil);
return *stop;
}];
return (latestAppointmentIndex == NSNotFound) ? nil : [self.appointments objectAtIndex: latestAppointmentIndex];
}
#end

Resources