I am using Core Data and have a subclass of NSManagedObject called Person (which is an entity in Core Data). The Person object has several properties (e.g. firstName, lastName, etc.) and relationships (e.g. friends, coWorkers). Some of these attributes are optional, whereas others are mandatory.
Given a Person object how can I efficiently determine whether a particular property or relationship is optional or mandatory?
I want to try and avoid having to do some kind of inefficient loop through each attribute of the Person object, as I will need to call the code fairly often, so something similar to the pseudocode below would be perfect:
if ( [[aPerson getProperty:#"firstName"] isOptional] ) {
// do stuff
}
...but I am not sure whether it could be done that simply. The above pseudocode would also only check properties, so I am guessing something else would need to be done in case the attribute being checked is a relationship.
I realize that it is probably more efficient to do a single loop and store the name of all the mandatory attributes in an array (then just check that array for the presence of the attribute name) and that's what I am doing at the moment, but unfortunately it doesn't really work with the structure of my code (thus the need for a more "on-demand" approach).
Any help would be greatly appreciated :)
You can write a function in your base subclass
NSDictionary *d = [[self entity] attributesByName];
NSAttributeDescription *attr = (NSAttributeDescription *)[d objectForKey:#"test"];
BOOL isopt = [attr isOptional];
Related
Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}
I'm developing an iPad App and need some help.
Through a button within my App I want to create one object at a time.
So every time the button is touched one object should be created.
The problem I have is: I want to assign each object a dynamic name to identify this object.
This would be something like: form0, form1, form2, ..., formN.
This Name corresponds to an instance variable within every object.
So the form1 instance has a number attribute which is 1.
But how do I assign this form1, form2, etc. to a new instance?
I tried to initialize a new instance with the return of a method which creates the formX-String:
-(NSString*)giveMeName{
NSString* simpleName = #"form";
NSString* newName = [simpleName stringByAppendingString:[NSString stringWithFormat:#"%d", questionCounter]];
return newName;
}
where questionCounter is a variable which holds the int identifier for both formX and the instance number attribute.
But when I want to initialize a new instance with this function as name it's not working:
TSForm* [self giveMeName] = [[TSForm alloc] initWithInt:questionCounter headline:headlineText intro:introText];
Obviously I got something wrong with the inner working of Objective-C.
Please help me out.
what you're trying to do isn't really possible. One way that you could achieve the affect you're looking for is using an NSDictionary. For every TSForm object you create, you add that object to the dictionary with the key of the giveMeName return value.
So you start by creating your dictionary:
NSMutableDictionary *formDict = [[NSMutableDictionary alloc] init];
Then, every time you create an object, add it to the dictionary:
id *newTSForm = [[TSForm alloc] init]; // Or however you create a TSForm
[formDict setObject:newTSForm forKey:[newTSForm giveMeName]];
Then when you want to pull out the form you're looking for, you just ask the dictionary based on the name you provided:
[formDict valueForKey:nameOfForm]; // nameOfForm is the name provided by giveMeName
Hope this helps!
use NSMutableArray and keep adding your items there.
Even if what you are trying to do is technically possible, that's using tricsk in low-level objective-C runtime and KVC stuff and so on for nothing.
Using a simple NSMutableArray to keep track of all you instances (and using the index in the array to know which form you are dealing with) is the way to go.
I don't think you really need your unique identifier stuff for that (if so, you are probably thinking about your project the wrong way), as long as you have a way in your code to differentiate each form and manipulate them (the first form created will then be at index 0, the second at index 1… of your NSMutableDictionary)
If you really need this special unique identifier anyway for some strange reason, you can still use an NSMutableDictionary and use the unique identifier as your key of your dict and the form as the associated value. But you should probably think twice about your architecture ad the real need for this before, as it seems quite strange app architecture/design to do so based on your description of your needs in your question.
What you are looking for is some kind of variable variable, which don't really exist in objective-C.
This question (Objective C Equivalent of PHP's “Variable Variables”) has some different suggestions for getting similar results.
I am little bit backward in knowledge on these three topics: NSMutableDictionary, NSEnumerator, NSMutableSet. When I want to use them, it feels very hard, even when I've gone through the developer documentation.
Is there any sample code to understand it clearly for all three topics?
Please help me.
Thank you,
Madan Mohan.
The best way to understand these depends on what your prior experience is. NSDictionary is exactly what it sounds like: a dictionary. What that means is that given a key (or a headword, as in a dictionary), you can look up a value (or definition):
For instance, this dictionary gives information about my dog:
KEY VALUE
-------------------------------------------
#"name" #"tucker"
#"species" #"canis lupus familiaris"
#"favorite" #"biscuits"
With a dictionary dogInfo containing that information, we could send [dogInfo objectForKey:#"name"] and expect to receive #"tucker".
The difference between NSDictionary and NSMutableDictionary is that the latter allows changes after initialization. This lets you do things like [dogInfo setObject:#"fetch" forKey:#"game"]. This is helpful for maintaining state, memoizing referentially transparent requests, etc.
NSSet is a way to have a bunch of objects, with a few important bits: there is no defined order to those objects, and there can only be one of each object (no duplicates). Use NSSet for when you need to contain unique, unordered objects. NSMutableSet is the variant of NSSet which allows for changes (such as adding or removing objects) after initialization.
NSEnumerator is a bit more complicated, but you usually won't need to deal with it unless you are writing your own libraries, are coding archaically, or are doing complex enumerations. Subclasses of NSEnumerator are used by collection classes, such as NSDictionary, NSArray, and NSSet, to allow their objects to be enumerated. Usually, you'd just enumerate over them using a foreach-loop, since they all implement <NSFastEnumeration>. But sometimes, you'll want to do more specific things, like enumerate over the objects (instead of the keys) of a dictionary, or enumerate over an array in reverse. This is where instances of NSEnumerator (usually defined as properties on your collection objects) will become helpful.
Update
Justin in the comments pointed out that NSEnumerator conforms to <NSFastEnumeration>; that means, the chances are next-to-nothing that you'll need to know how to use an NSEnumerator; you can just do a foreach loop over the enumerator itself, like so:
for (id object in [dogDict objectEnumerator]) {
// doing something with the object, disregarding its key
}
I am really stuck with these two things.
What I am trying to do:
My entity is simple. It's a "record".
It has a "name (NSString)" and "parent (relationShip)"
"parent" connect to itself, entity "record".
Ok, now I want to create "parentRecord" and "simpleRecord".
I try to do with that code:
groupRecord = (record *)[NSEntityDescription insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
groupRecord.name = GroupTextField.text;
[self saveContext];
It's "parentRecord", I save it for a future use, and catch in "groupRecord" variable.
Now I have to create a "simpleRecord". This is a code:
record *newRecord = (record *)[NSEntityDescription
insertNewObjectForEntityForName:#"record"
inManagedObjectContext:self.managedObjectContext];
newRecord.name = textField.text;
[newRecord setMyParent:groupRecord]; //and it crashes here!
I rearranged this code, so *I don't do [self saveContext]; * in "parentRecord".
Just use it from variable groupRecord. And save it in "childRecord" block. Then all is fine. Records save to storage and I can read it from there.
Why does it happens? What should I do, if I want to create "parentRecord" first, SAVE IT ,and later - "childRecord"?
Why can't I use previously saved object? NSManagedObjectContext is the same - what's wrong?
I am good enough with "classic" SQL, but Core Data is killing my brain.
Thanks to everyone.
Update:
Look, saveContext is out of reasons to crash. Here is:
Create parent entity.
Set it to variable of appDelegate.
Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. App crashes!
And:
Create parent entity.
Set it to variable of appDelegate.
///////////Save context (for a parent).
Create childEntity.
Set parentProperty from variable of appDelegate. No any crash.
Savecontext this time.
All is fine now.
Parent property - is just a name of the property. It is not some additional setup for a parent in MOM file.
I want to do entity with hierarchy.
And there is NO some additional methods, that Xcode create for me - just a properties.
Okay it sounds like you have a simple data model that looks like this (pseudocode):
Record{
name:string
parent-->Record
}
This is dangerous because there is no reciprocal relationship. This can lead to orphaned objects and compromise the integrity of the object graph. Instead use:
Record{
name:string
parent<--(optional)-->Record.child
child<--(optional)-->Record.parent
}
Now, you have a simple, one dimensional linked list like an array or set. Except for the topmost record object, every record object has a parent and expect for the bottommost object each has a child. To assign one to each you would do:
Record *firstRec; //assuming you have created a custom class for Record
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec,name=someOtherText;
firstRec.child=secRec;
[self saveContext];
Now if you want a tree structure in which each parent can have more than one child, you would have an object model like so:
Record{
name:string
parent<--(optional)-->>Record.child
child<<--(optional)-->Record.parent
}
Your insertion and assignments then change to:
Record *firstRec;
Record *secRec;
firstRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
secRec=[NSEntityDescription insertNewObjectForEntityForName:#"Record"
inManagedObjectContext:self.managedObjectContext];
//-------------------------------------^
firstRec.name=someText;
secRec.name=someOtherText;
[firstRec.addChildObject:secRec];
// or
secRec.parent=firsRec;
[self saveContext];
The reason is that a to-many relationship requires a method to add the new object to set. Which cannot be done with a simple assignment. The child, however, only has one parent so it can use a simple assignment. Since the relationship is reciprocal, assigning to one object automatically assigns to the object on the other side of the relationship.
That is how it should work. The errors you are seeing most likely come from having the wrong object model. If you have one-to-one, required relationships like this:
Record{
name:string
parent<--(required)-->Record.child
child<--(required)-->Record.parent
}
... you will encounter problems when you try to save if either a parent or child is missing. Likewise, if you try to assign multiple objects to a to-one relationship, you can get the error you are seeing.
You should never use the cast when doing an insertion because if you have a mismatch between the assigned class and the cast class, the runtime will force the other class into the cast causing all kinds of strange errors.
I can't say for certain exactly what your problem is because I can't see your object model. This however, should point you in the right direction.
Would you share code for "saveContext" and for "setMyParent"?
NSManagedObjectContext has -(BOOL)save:(NSError**)error method. Is that being called within "saveContext"?
And, if your relationship is called "parent", then you should be setting relationship with something like -addParentObject: ... which would be declared in your Record.h file. Xcode will do this for you, if you do things in a certain order. Otherwise, you will need to write the method declarations yourself.
I have a Core Data Entity that needs to hold onto the NSManagedObjectID of some other Entity. To do so I was considering converting the ObjectID to a string that is an approved type of an NSManagedObject attribute.
I can read from the documentation that I can get a URI representation of the ID by:
NSURL *uriID = [[myEntity objectID] URIRepresentation];
I can then convert this URL to an NSString by:
NSString *stringID = [uriID absoluteString];
This I can persist to my NSManagedObject's NSString attribute.
Now what happens when I need to go the other way?
I would like to be able to do something like this:
if([myManagedObject objectID] == value)
where value is the NSManagedObjectID that I converted to an NSString earlier.
To shed a little more light on the why: I need to be able to have an Entity object hold and persist the ObjectID of another Entity object, so that I later on can go: this Objects last "interaction" was with this Entity.
Hope someone can help me get this working:)
Thank you
Why not just establish a to-one relationship property in Object called interaction, which points to an instance of an Entity — and vice verse, a to-many relationship from Entity to Object called interactions? This solves the problem pretty neatly, without all the conversion methods.
But you might also look at the -managedObjectIDForURIRepresentation: and +URLWithString: methods to go the other direction.