Navigating a to-many relationship with Core Data - objective-c

How do I navigate a Core Data to-many relationship in Objective-C?
I have an Event model and each instances has many EventOccurace objects that are exposed via an occurances [sic] relationship. Apple's docs say that the standard property accessor should be available but I keep getting compile-time errors:
DetailViewController.h
#interface DetailViewController : UIViewController <UISplitViewControllerDelegate>
#property (strong, nonatomic) id detailItem;
#end
DetailViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSSet * foo;
foo = [[self detailItem] occurances];
///No known instance method for selector 'occurances'
foo = self.detailItem.occurances;
///Property 'occurances' not found on object of type 'id'
//try casting to NSManagedObject to access
NSManagedObject * casted = (NSManagedObject *)self.detailItem;
foo = casted.occurances;
///Property 'occurances' not found on object of type 'NSManagedObject *'
foo = [casted occurances];
///No visible #interface for 'NSManagedObject' declares the selector 'occurances'
}

To use the (dynamically created) accessor methods, you have to create subclasses of NSManagedObject for your entities.
This is easily done in Xcode: Select the entities in the model editor and choose "Editor -> Create NSManagedObject subclass ..." from the menu. Include the header files (Event.h, EventOccurance.h) in your source code. Then
Event *event = self.detailItem;
NSSet *foo = event.occurances;
should work.
Alternatively, you can use Key-value coding:
NSSet *foo = [[self detailItem] valueForKey:#"occurances"];
which works even without the managed object subclasses.

Some things that could be not working:
First of all check that your ManagedObject classes have been generated.
Second, you should specify the type of the detailItem somewhere. Or in the .h, or by casting it to a type in the .m. Now its just an "id", the compiler cant know which is its type.
Third, this is one possible way of accesing it:
NSArray * occurances = [self.detailItem.occurances allObjects];
for(EventOccurance * ocu in occurances){
//blablabla
}

Related

Objective-C method return type with generics and Xcode autocomplete

I have custom collection class with generic parameterisation. I create collection of cats, add cat. When I try to get cat back Xcode is showing error: "Property 'name' not found on object of type 'id'". This is nonsense, because Cat has property name and MyCustomCollection doesn't return id but ObjectType. How do I declare method so the autocompletion could understand which type is returning by the method?
MyCustomCollection *collection = [[MyCustomCollection<Cat *> alloc] init];
[collection addCustomObject:[[Cat alloc] init]];
NSString *string = [collection customObjectAtIndex:0].name; // Property 'name' not found on object of type 'id'
MyCustomCollection.h file
#interface MyCustomCollection<ObjectType> : NSObject
-(ObjectType)customObjectAtIndex:(NSUInteger)index;
-(void)addCustomObject:(ObjectType)object;
#end
Cat.h file
#interface Cat : NSObject
#property (nonatomic, strong) NSString *name;
#end
The problem isn't with the method declaration. It is the declaration of the collection variable. You have to tell the compiler what type of objects are in the collection:
MyCustomCollection<Cat *> *collection = [[MyCustomCollection<Cat *> alloc] init];
Otherwise it won't know what sort of objects the collection variable references and assumes that they are of type id (hence the error).
You theoretically can also cast the result of customObjectAtIndex, but that seems to defeat the purpose of using generics.

Subclassing iOS Model Objects - Appropriate Design Pattern

I fear this is a rather simple question, but after much googling I think I have overshot my intended result. I believe my question to be related to a design pattern, but alas I could be wrong.
My application calls an RESTful API and gets back what amounts to a list of model objects represented by an NSDictionary. Each of which I will call NNEntity. There are (conceptually) multiple different subtypes of NNEntity. All subtypes of NNEntity share the property of entityID, but each have their own unique properties as well. All instances of NNEntity have a method called readFromDict:(NSDictionary *)d that populates their respective properties. This method is enforced by a protocol that all NNEntity subtypes conform to. It looks like this:
//NNEntity.h
#interface NNEntity : NSObject <NNReadFromDictProtocol>
#property (nonatomic, strong) NSString *entityID;
#end
//NNEntity.m
#implementation NNEntity
- (void)readFromDict:(NSDictionary *)d {
//set common properties from values in d
self.entityID = [d objectForKey:#"ID"];
}
#end
//NNSubEntity1.h
#interface NNSubEntity1 : NSEntity <NNReadFromDictProtocol>
#property (nonatomic, strong) NSString *favoriteColor;
#end
//NNSubEntity1.m
#implementation NNSubEntity1
- (void)readFromDict:(NSDictionary *)d {
[super readFromDict:d];
//set unique properties from values in d
self.favoriteColor = [d objectForKey:#"colorPreference]:
}
#end
//NNSubEntity2.h
#interface NNSubEntity2 : NSEntity <NNReadFromDictProtocol>
#property (nonatomic, strong) NSString *middleName;
#end
//NNSubEntity2.m
#implementation NNSubEntity2
- (void)readFromDict:(NSDictionary *)d {
[super readFromDict:d];
//set unique properties from values in d
self.middleName = [d objectForKey:#"middleName]:
}
#end
I have read various pieces on the use of a Factory or Builder Desing pattern for similar use cases but I am curious if that is necessary in this rather simple case. For example, does my current code end up creating both and instance of NNEntity and NNSubEntity2 if I were to call something like this:
NNEntity *newEntity = [[NNSubEntity2 alloc] init];
//assume dict exists already and is properly keyed
[newEntity readFromDict:dict];
I assume not, but would newEntity have both the common property of entityID as well as the unique property of middleName set correctly? Also, much appreciated if you have thoughts on a better or more efficient design approach.
This looks like exactly how you should be doing it. You have a base class which read in the common attributes, and subclasses which read in their specific attributes.
For example, does my current code end up creating both and instance of NNEntity and NNSubEntity2? NNEntity *newEntity = [[NNSubEntity2 alloc] init];
Nope. When you run this, you instantiate NNSubEntity2 and store the result in a variable typed by it's superclass, which is totally valid. This allows you to call any methods defined on the superclass, but the actual instance is still of the subclass.
Would newEntity have both the common property of entityID as well as the unique property of middleName set correctly?
It sure would. It inherits the instance variables, properties and methods in the superclass.
Rest assured, as far as I can tell this looks sound and is a pattern I've used before.
I do it like this.
// NNEntity.h
#interface NNEntity : NSObject
#property (nonatomic, retain) NSString *entityId;
#end;
// NNEntity.m
#implementation NNEntity
#end;
// NNEntity+KVC.h
#interface NNEnity (KVC)
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
#end
// NNEntity+KVC.m
#implementation NNEntity (KVC)
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
// Handle this as appropriate to your app.
// A minimal implementation will throw an exception.
}
#end
And similarly for your various subclasses. You don't (necessarily) need the category on your subclasses.
Then, given NSDictionary *dict with your stuff in it:
NNEntity *entity = [[NNEntity alloc] init];
[entity setValuesForKeysWithDictionary:dict];
Violá! You're done. There are some criticisms of this method, but given a strong implementation of setValue:forUndefinedKey:, I think it's safe and incredibly flexible.
The secrets are in Apple's beautiful Key-Value Coding technology. Essentially, setValuesForKeysWithDictionary: iterates the keys the dict you give it, and for eachinvokes setValue:forKey: in its receiver. It looks something like this (though I'm sure Apple optimizes it under the hood):
-(void)setValuesForKeysWithDictionary:(NSDictionary *)dictionary {
NSArray *keys = [dictionary allKeys];
for (NSString* key in keys) {
[self setValue:[dictionary valueForKey:key] forKey:key];
}
}
I also like this approach because a conversion to CoreData is simple; when you tell CoreData to 'render' your model, it simply overwrites your stubbed model classes, keeping your KVC Category intact. What is more, if your implementation of setValue:forUndefinedKey: is smooth, you can make model changes to your backend without crashing the app (this is a bit of a no-no, but it's not much different from your factory solution).
Of course, I have not addressed your need to selectively choose which class to instantiate. But that is a larger design issue that could be affected even by the design of your API and backend. So I defer.
Also, as you noted in your comment below, the property names must match up. This is a show-stopper for some developers, especially so if you cannot control both the backend and the client.
Give it a try. Feedback is welcome.

share NSArray between different UIViewControllers

I am making an application that uses a webService to get data in a JSON format... I get the data I parse them into a object NSArray ... and i use it .. it works fine ...
Now, if the user clicks a button I need to send him to an other Uiview ... which contains more data about the clicked object ..
The problem is here ... I don't want to request again and download the result from the server ... because i already did ... All I want is to have access to that NSArray that I have in the first UIViewController.
You can add on AnotherView.h another property:
#property (nonatomic, retain) NSArray *jsonData;
On AnotherView.m synthesize it. When you are going to to call AnotherView from InitialView, you can set jsonData with the data you retrieved on InitialView.
Create a custom initializer in your other view controller like so:
#import <UIKit/UIKit.h>
#interface OtherViewController : UIViewController
#property (nonatomic, strong) NSArray *myArray;
- (id)initWithArray:(NSArray *)anArray;
#end
Then implement it like so:
#import "OtherViewController.h"
#implementation OtherViewController
#synthesize myArray=_myArray;
- (id)initWithArray:(NSArray *)anArray {
if (!(self = [self initWithNibName:#"OtherViewController" bundle:nil]))
return nil;
if (!anArray) {
#throw [NSException exceptionWithName:#"OtherViewControllerBadInitCall" reason:#"array is nil" userInfo:nil];
}
_myArray = anArray;
return self;
}
//...
#end
You can then init and display your controller like so:
OtherViewController *otherViewController = [[OtherViewController alloc] initWithArray:greatJSONArray];
[self.navigationController pushViewController:otherViewController animated:YES];
There you go.
You can set the array as the property. You can either create a new class and set the array as the property and after you fetch the array, set the property. Or, you can create a property of the existing UIVIewController Class and pass the object.
Either way, you have to set property.
You could define a new property in your second ViewController that holds an NSArray and pass the firt array to the second ViewController before show it.
Well you have not outlined whether you send the data forward or backward. In the later case you will need to implement protocol and delegate(Define your own protocol) but for the prior case you just need to create the property of the Object you want to access in any other class. In case of web-services it is better to use protocol and delegates if u abide by the norms of MVC architecture.

Objective C - respondsToSelector for dynamic properties

I am currently facing the problem to check whether a property of an Object (NSManagedObject) exists or not.
Unfortunately the method
[[MyObject class] respondsToSelector:#selector(myProperty)];
always returns NO.
I think it's because the property generated by CoreData is a new style property ala
#property (nonatomic, strong) NSString *myProperty
So any ideas how to solve this issue?
I would really appreciate all of your suggestions ;)
Thanks in advance!
Alex
[[MyObject class] respondsToSelector:...] asks whether the metaobject responds to that selector. So, in effect, it asks whether there is a class method with that selector. Your code would return YES if you had:
+ (NSString *)myProperty;
It returns NO because you have the equivalent of the instance method:
- (NSString *)myProperty;
You need to call respondsToSelector: on an instance of your class.
You could normally use instancesRespondToSelector: directly on the metaclass (so, [MyObject instancesRespondToSelector:...]) but Core Data synthesises the relevant method implementations only when you create an object, so that's a non-starter. You could however create an instance via the normal NSEntityDescription route and test respondsToSelector: on that.
Since it's all Core Data, an alternative would be to ask the NSManagedObjectModel for the relevant NSEntityDescription via its entitiesByName dictionary and inspect the entity description's propertiesByName dictionary.
The only cases I've required this has been to set things dynamically so I am only looking for the setter. I am just composing the signature for the setter and then testing that it exists and then using it.
NSArray * keys = [myObject allKeys];
for(NSString * key in keys)
{
NSString * string = [NSString stringWithFormat:#"set%#:", [key capitalizedString]];
SEL selector = NSSelectorFromString(string);
if([myObject respondsToSelector:selector] == YES)
{
id object = [dict objectForKey:key];
// To massage the compiler's warnings avoid performSelector
IMP imp = [card methodForSelector:selector];
void (*method)(id, SEL, id) = (void *)imp;
method(myObject, selector, object);
}
}
This code satisfies a need where you may not be digesting all the data you receive in the dictionary.
In this case it was sparse json, so some data may not always exist in the json so stepping thru myObjects attributes looking for their corresponding key would just be a lot of wasted effort.
Are you synthesizing the property in the class file?
#interface SomeClass : NSObject
{
#property (nonatomic, strong) NSString *myProperty
}
#end
#implementation SomeClass
#synthesize myProperty;
#end

Objective-C : adding attribute to a category

I have built a category for NSDate and I would like to encapsulate an attribute in this category to hold some data. But I can't achieve adding this attribute, only methods.
Is there any way to achieve this ?
Thank you.
Here some Code:
Filename: NSObject+dictionary.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#interface NSObject (dictionary)
- (NSMutableDictionary*) getDictionary;
#end
Filename: NSObject+dictionary.m
#import "NSObject+dictionary.h"
#implementation NSObject (dictionary)
- (NSMutableDictionary*) getDictionary
{
if (objc_getAssociatedObject(self, #"dictionary")==nil)
{
objc_setAssociatedObject(self,#"dictionary",[[NSMutableDictionary alloc] init],OBJC_ASSOCIATION_RETAIN);
}
return (NSMutableDictionary *)objc_getAssociatedObject(self, #"dictionary");
}
Now every instance (of every class) has a dictionary, where you can store your custom attributes.
With Key-Value Coding you can set a value like this:
[myObject setValue:attributeValue forKeyPath:#"dictionary.attributeName"]
And you can get the value like this:
[myObject valueForKeyPath:#"dictionary.attributeName"]
That even works great with the Interface Builder and User Defined Runtime Attributes.
Key Path Type Value
dictionary.attributeName String(or other Type) attributeValue
You can't add instance variables in categories.
However, you can add storage for your attribute to an object using associative references. Note that if you need to add more than one attribute, rather than adding an associative reference for each, you're probably better off adding a single reference to (say) an NSMutableDictionary, CFMutableDictionaryRef, or NSMapTable and using that for all of your attributes.
objc_setAssociatedObject() and objc_getAssociatedObject()
If you want to add attribute to class, you can try to use github.com/libObjCAttr. It's really easy to use, add it via pods, and then you can add attribute like that:
RF_ATTRIBUTE(YourAttributeClass, property1 = value1)
#interface NSDate (AttributedCategory)
#end
And in the code:
YourAttributeClass *attribute = [NSDate RF_attributeForClassWithAttributeType:[YourAttributeClass class]];
// Do whatever you want with attribute
NSLog(#"%#", attribute.property1)