Get property class giving the property name - ios

What I need
I have a bunch of classes generated from my data model that inherit from NSManagedObject. I need a way to get, for any of theses classes, a dictionary of a string of a property name as the key, and the string of it's type as the value, for every property name passed in an array.
Or better, in code:
// what I have - generated code
#interface ClassA : NSManagedObject
#property (nonatomic, retain) ClassX *propX;
#property (nonatomic, retain) ClassY *propY;
#end
#implementation ClassA
#dynamic propX;
#dynamic propy;
#end
// what I need
// the method
-(NSDictionary*)dictFromPropNameToPropType:(NSArray*)props {
//dict being something like #{ #"propX" : #"ClassX", #"propY" : #"ClassY" };
return dict;
}
// call
dictFromPropNameToPropType(#[#"propX", #"propY"]);
The logic of the dictionary creation is on me. I need a way to get the property class name as a string, giving it's name.
What I've tried
// a instance method of my ClassA
SEL selector = NSSelectorFromString(propertyNameAsString); // like #"propX"
id object = [self performSelector:selector];
Class class = [object class];
NSString *className = NSStringFromClass(class);
and also tried something with dictionaryWithValuesForKeys, but it seems to use the same mechanism and result in the same kind of errors.
No succes there. I get the "class not key value coding-compliant" error for the key passed in as propertyNameAsString, even thou I have the propX property in my ClassA
What I've researched
I've looked the Apple tutorials about KVC and KVO, and also the one about the Runtime (to learn something about the #dynamic auto-generated property). But didn't figure it out on my own.
Edit
With Martin R's answer, I've got this code:
NSMutableDictionary *result = [NSMutableDictionary new];
NSEntityDescription *selfEntity = [self entity];
NSDictionary *relationshipsByName = selfEntity.relationshipsByName;
for (NSString *relationDescription in relationshipsByName) {
NSRelationshipDescription *relationshipDescription = relationshipsByName[relationDescription];
NSEntityDescription *destinationEntity = relationshipDescription.destinationEntity;
result[relationDescription] = destinationEntity.managedObjectClassName;
}

You have subclasses of NSManagedObject, so you can inspect the objects entity, which is
a NSEntityDescription.
From the entity description, get the propertiesByName, which is
a dictionary with the property names as key. The values are NSAttributeDescription
or NSRelationshipDescription objects.
NSAttributeDescription has a method attributeValueClassName, which is the class
representing the attribute (as a string).
NSRelationshipDescription has a method destinationEntity which describes the target
entity and has a managedObjectClassName method.

Related

Objective-C: work with weak references

I have a few classes:
Book, Publisher, Author and Genre.
So here is the main class Book.h:
#import "Publisher.h"
#import "Author.h"
#import "Genre.h"
#interface Book : NSObject
#property (nonatomic, strong) NSString *bookName;
#property (nonatomic, strong) Author *author;
#property (nonatomic, strong) Publisher *publisher;
#property (nonatomic, weak) Genre *genre;
- (instancetype)initWithBookName:(NSString *)name andAuthorName:(NSString *)authorName
andPublisher:(NSString *)publisherName andGenreName:(__strong NSString *)genreName;
- (NSString *)description;
#end
and his implementation Book.m:
#import "Genre.h"
#import "Book.h"
#import <Foundation/Foundation.h>
#implementation Book
- (instancetype)initWithBookName:(NSString *)name andAuthorName:(NSString *)authorName
andPublisher:(NSString *)publisherName andGenreName:(__strong NSString *)genreName{
self = [super init];
if (self) {
_bookName = [name copy];
_author = [[Author alloc] initWithAuthorName:authorName];
_publisher = [[Publisher alloc] initWithPublisherName:publisherName];
_genre = [[Genre alloc] initWithGenreName:genreName];
}
return self;
}
- (instancetype)init {
return [self initWithBookName:#"unnamed" andAuthorName:#"unnamed" andPublisher:#"unnamed" andGenreName:#"unnamed"];
}
- (NSString *)description {
return [NSString stringWithFormat: #"Book: %#, Author: %#, Genre: %#", self.bookName, self.author, self.genre];
}
#end
I have delegate class - Genre, so to avoid strong reference cycles, a Book's Genre property must be weak.
At this point in the Book.m initializer:
_genre = [[Genre alloc] initWithGenreName:genreName];
it will be nil, because the Genre instance will be deallocated right after assignment.
According to Dan comment, here is my Genre.h:
#import <Foundation/Foundation.h>
#class Book;
#interface Genre : NSObject
#property (nonatomic, strong) NSString *genreName;
#property (nonatomic, strong) NSArray <Book *> *books;
- (instancetype)initWithGenreName:(NSString *)name andBooks:(NSArray <Book *>*)books;
- (instancetype)initWithGenreName:(NSString *)name;
- (NSString *)description;
#end
My question is "What is the best way to store genre object (genreName -> Genre constructor -> genre object) at weak property genre and how do I can store it without using constructor for assignment to weak property?".
SOLUTION: In my case it was collection of Genre and I take my weak property reference to one of objects from my collection.
Genre * genre1 = [[Genre alloc]initWithGenreName:#"Comedy"];
Genre * genre2 = [[Genre alloc]initWithGenreName:#"Drama"];
Genre * genre3 = [[Genre alloc]initWithGenreName:#"Fantastic"];
Genre * genre4 = [[Genre alloc]initWithGenreName:#"National"];
NSArray <Genre*> *genres = #[genre1, genre2, genre3, genre4];
Book *book1 = [[Book alloc] initWithBookName:#"Book #3!" andAuthorName:#"Grinch Burs" andPublisher:#"Ableton" andGenre:[genres objectAtIndex:0]];
The rule to remember is - strong properties increase the reference count, while weak ones do not - and when the reference count gets to 0, a proper is deallocated. So in the case of Genre - at your point in the code, there are no strong references to it so it is deallocated. The solution really is to have the Genres 'owned' by another class. This class would manage the genres, creating them and keeping strong references to them, perhaps in for eg an array of Genres. Your 'strong' genre would be passed in with the initializer, and then the weak reference is the correct approach, preventing a retain cycle, but the dealloc is prevented by the strong property that the Genre property already has - does that make sense?
In a way it makes sense to think of your objects as needing an 'owner' class, where strong references are defined that keep them alive. Then when passed to other classes like your Book class, those have weak references, which prevents the retain cycle as you say. The book class isnt the owner, but someone else is - so it doesnt go away.
One solution would be to make the genre property a strong reference.
If you really need to make Genre a weak reference,
you can solve this by storing all genres in a table and access them statically with something like this:
_genre = [Genre forName:genreName]
The static forName method would then look the correct genre up in a table of all genres. As storing the genre in a table would retain the object, it will not be released immediately on assignment.
#implementation Genre
static NSDictionary* genres;
+ (void) initGenres {
// initialize the dictionary and insert all genres
// or just initalize the dictionary and insert genres on demand
}
+ (Genre*) forName: (NSString*) genreName {
if (!genres) {
[Genre initGenres];
}
//lookup the genre in the dictionary and return it
}
#end
A weak reference does not create a reference count. If there are only weak references to an object, the object will be deallocated. That means if you want an object to stay alive, you either use a strong reference, or you store it somewhere else using a strong reference.
You use weak references to avoid reference cycles, and for objects that are held elsewhere at the moment but might disappear at some point. In your case, using a weak reference is something you won't get working properly.

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.

PFObject Subclass Getters and Setters?

I have a simple PFObject subclass setup. MyPFSubclass. It looks something like this:
#interface MyPFSubclass: PFObject <PFSubclassing>
+ (NSString *)parseClassName;
#property (retain) NSString *myString;
#end
#import <Parse/PFObject+Subclass.h>
#implementation MyPFSubclass
#dynamic myString;
+ (NSString *)parseClassName {
return #”MyPFSubclass”;
}
#end
This works great, as expected, until what I discovered today.
I can set the myString value and read and write as expected, an NSLog shows the data to be what I set it to.
MyPFSubclass *obj = [MyPFSubclass new];
obj.myString = "#hello";
//prints expected value as set above
NSLog(#"%#", obj.myString);
obj[#"myString"] = "#hello";
//prints expected value as set above, again
NSLog(#"%#", obj[#"myString"]);
However, if I do the following, I do not get the changed result.
obj[#"myString"] = #"Hello";
//prints original value, not as set above
NSLog(#"%#", obj.myString);
It seems the key name setters and getters are independent to the subclass setters and getters. I don't want this!
As an example, I have a subclassed view that takes a generic PFObject and key name from which it can get and set values.
How can I resolve this? Any reason why I cannot mix usage of the subclass and keyname getters and setters?
Not an exact answer, however it is a solution.
I found that in the scenario of using a PFObject subclass, using setValue:forKeyPath is more reliable than using Parse's own bracketing syntax. i.e. myObject[#"myAttribute"].
Where this latter appears non-interchangeable with subclass properties, I found replacing it with setValue:forKeyPath works.

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

Accessing an array from another class

I have an array called client in one of my classes and I want to use the information in that array in another class that i have. I have set up the property and synthesized the array in my first class. The code for my first class is
#synthesize client;
...
- (IBAction)testing:(id)sender {
NSString *textContent = myTextView.text;
textContent = [textContent stringByReplacingOccurrencesOfString:#" " withString:#""];
client = [textContent componentsSeparatedByString:#"."];
NSLog(#"%#", client);
}
In my second class I tried importing the h file for my first class and then just accessing the array. The code that I am using is
- (IBAction)ButtonStuff:(id)sender {
ArrayManipulationViewController *myClass = [[ArrayManipulationViewController alloc]init];
NSLog(#"Second Interface");
NSArray *test = myClass.client;
NSLog(#"%#", test);
}
To access object from multiple classes, a common approach is to declare the object in the parent class, and then pass a shared instance of that object to all child classes that require access. For instance, you could declare the array in the AppDelegate, and set array properties in your subclasses, and pass the instance of the array from the AppDelegate to all your subclasses.
Eg: create an NSArray (myArray) in your app delegate, then in the AppDelegate implantation, pass the myArray instance to your sub view controllers using properties.
Or, if you'd prefer; you can declare the array in your first class, and then pass the array instance from your first class to your second class using properties. Then, any changes made in your second class will be available in your first class, since the INSTANCE is the same.
UPDATED ANSWER:
For the second approach, you're best declaring the array in your first class implementation, and then when you instantiate the second class, pass the instance of the array to the second class using properties. In this example, you'll need to have an NSArray property in your second class to be able to pass the array over to it using [secondClassInstance setClient: client];
Your second class interface might look like this:
#interface SecondClass : NSObject
{
NSArray *client;
}
#property (nonatomic, retain) NSArray *client; // don't forget to synthesize
#end
Then, in your first class, you can do the following to pass over your instance of the array:
NSArray *client = [[NSArray alloc] initWithObjects:#"Object 1", #"Object 2"];
//...
SecondClass *secondClass = [[SecondClass alloc] init];
[secondClass setClient: client]; // passing the original client instance here
// don't forget to release secondClass instance when finished

Resources