Objective-C: Accessing Member Objects that are in an array?!? I think - objective-c

Totally new to Obj-C, so thanks for patience. :P
Because I'm beginner, I will use the car example. Easier for me to understand.
I have an object, Car. It has two member objects, tire and engine.
Tire and engine have their own member variables too, but they are just int with various names (like pressure, treadDepth).
In all these cases, I have synthesized accessor methods. I'm not sure about accessor methods for objects, so I just did #property id engine / #property id tire. Hope that is right!
Now, I can do dot.notation style to access like: [car.engine cylinders]. Fine! Sending tire and engine messages works fine. I write methods, this notation seems to work.
But when I declare an array of objects like 4 tires for the car:
#interface Car : NSObject {
tire *tires[4];
}
I cannot send it message like this
[car.tire[0] setPressure: int];
It says accessing unknown tires getter method.
Basically I am wondering if someone can help me understand how to correctly access member variables of an object that is in an array.
Thanks!

You are trying to call a getter on car that doesn't exist. You can't return a C-style array by value anyway, so instead of just returning a Tire* pointer i'd rather use a NSArray in this case:
// header:
#interface Car : NSObject {
NSArray *tires;
}
#property (nonatomic, copy) tires;
// ...
// source:
#implementation Car
#synthesize tires;
- (id)init {
if ((self = [super init])) {
tires = [[NSArray alloc] initWithObjects:
[[[Tire alloc] init] autorelease],
// ...
nil];
// ...
}
return self;
}
- (void)dealloc {
[tires release]; // don't forget to clean up
// ...
}
Now you could use the getter:
[[[car.tires] objectAtIndex:0] setPressure:0];

Why not put all of your tire objects into an NSArray or NSSet? Or, since you know there are only four, you could simply define frontLeftTire, frontRightTire, etc. properties.

Well you could use Objective-C style arrays. Then you would have something like:
NSArray *tires = [NSArray arrayWithObjects: tire1, tire2, tire3, tire4];
And then you would access them as:
[tires objectAtIndex:0];
That's assuming you are using the synthesized methods as described. I'm not sure from your question, but it seems like you might want to define a class "tire" for these objects (rather than just a method, which is all I see above) that inherits from NSObject, or maybe from your own class CarPart, etc. Then you allocate 4 tires in a loop and call an init method that sets up some default state (hopefully better than the donut that came as the spare in my car) and then add them to your array in "Car" when you initialize a car.

Related

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.

How do I extend an NSArray?

Here's my try:
H file:
#interface Strings : NSArray
#end
M file:
#implementation Strings
- (id) init
{
[self initWithObjects:
#"One.",
nil];
return self;
}
#end
When I run I get this:
'NSInvalidArgumentException', reason: '* -[NSArray initWithObjects:count:]: method only defined for abstract class. Define -[Strings initWithObjects:count:]!'
This is what I did instead:
H file:
#interface Strings : NSObject
+ (NSArray*) getStrings;
#end
M file:
#implementation Strings
+ (NSArray*) getStrings
{
NSArray* strings = [[NSArray alloc] initWithObjects:
#"One.",
nil];
return strings;
}
#end
NSArray is a class cluster (link to Apple's documentation). This means that when you try to create an NSArray, the system creates some private subclass of NSArray. The NSArray class just defines an interface; subclasses of NSArray provide implementations of the interface.
You can write your own subclass of NSArray, but you have to provide your own storage for the objects in the array. You have to initialize that storage yourself. The error message is telling you this, by saying that you need to override initWithObjects:count: in your subclass. Your override needs to put the objects into whatever storage you allocate as part of your class implementation.
The NSArray implementation of the variadic initWithObjects: method is just a wrapper around initWithObjects:count:, so you don't have to implement initWithObjects:.
Deriving from NSArray is something you should avoid. From the documentation:
Remember that NSArray is the public interface for a class cluster and what this entails for your subclass. The primitive methods of NSArray do not include any designated initializers. This means that you must provide the storage for your subclass and implement the primitive methods that directly act on that storage.
What this means is that when you initialize an array, you don't get an instance of NSArray. You'll get an instance of a totally different class that merely has the same interface. That is why subclassing doesn't work the way you think it works: you'll have to completely implement the storage yourself. This is why the documentation further states:
Any subclass of NSArray must override the primitive instance methods count and objectAtIndex:. These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
Last but not least you would have had the initializing wrong anyway. You would have needed to call super:
- (id)init
{
self = [super initWithObjects:#"One", #"Two", nil];
if (!self) return nil;
return self;
}
But as I just said, it just doesn't work that easily. You'll get the same exception again. So you should simply avoid doing deriving from NSArray.
What you can do is add a category to add methods to all NSArray instances.
NSArray doesn't support being subclassed in this way. You can add a category, though, although that's not universally recommended.
See Objective C - Subclassing NSArray for more thoughts.
perhaps
self = [super initWithObjects:...];
You need to assign self, and call your superclass' init method.
if (self = [super initWithObjects:...]) {
...
}
return self;

Shared array between multiple classes

I have an array "myArr" which contains objects of custom class..e.g. objs of type MyClass
I need to share this array across multiple classes..
Could you please help me with the exact code that I should be using..
I have referred Singleton patter on Apple and other references, but it is all ver confusing to me...So it will be great if you could highlight the things/code that I need to add.
I recommend that you read up on object delegation.
#property (nonatomic,copy) NSArray *myArr;
On your other classes, implement a delegate object that will point to this class, then you could use:
NSArray *retrievedArray = [self.delegate myArr];
Edit: If you are interested to use only Singleton i believe it would be something along this way:
static MyClass *obj = nil;
On your class with the array, create a class method to return a Singleton object
+(MyClass*) sharedInstance {
if (obj) {
obj = [[self alloc]init];
}
return obj;
}
On your other classes you could just use
NSArray *retrievedArray = [[MyClass sharedInstance] myArr];
to get back the array.
Cheers.
I’d stay away from singletons. As the array is some kind of model (in the Model–View–Controller sense), other classes should depend on it:
#interface ControllerA : UIViewController {}
#property(retain) NSArray *array;
#end
#interface ControllerB : UIViewController {}
#property(retain) NSArray *array;
#end
Now the question changes into “how do I supply the depency.” You can do this using Interface Builder or supply the array when the depending objects are built. For example I sometimes have a method called setupObjectGraph in my application delegate that creates class instances and connects them together:
- (void) setupObjectGraph
{
mainController = [[MainController alloc] init…];
[mainController setThis…];
[mainController setThat…];
OtherController *bar = [[OtherController alloc] init…];
[bar setThis…];
[bar setThat…];
[mainController setBar:bar];
[bar release];
…
}
- (void) applicationDidFinishLaunchingOrWhatever
{
[self setupObjectGraph];
[window addSubview:[mainController view]];
[window makeKeyAndVisible];
}
This is not perfect (it does not scale very well), but it works for many applications and it’s much better than singletons. This sounds like a trifle issue, but it affects your overall design a lot, so it makes sense to think it through.

Binding to a relations property in Core Data

I'm new in Core Data, and i got a problem i can't get my head around how to do "the right way"
I'll try and examplify my problem.
I got a entity Car. And a list of all the cars in my program. The cars have some attributes, but they are not predefined. So for each car i want to be able to define some properties.
Therefore i have defined a new entity CarProperty, with a one to many relation with the car.
In the nscollectionview i would like to show some of the properties from the car, more specefic the number of kilometer (numKm) it has driven (if that property exist). So i want to bind it to a label. But how to do?
I can't say representedObject.properties.numKm, or representedObject.numKm.
How should I get around this?
Hope it makes sense.
This isn't an easy problem. The thing is, Core Data doesn't know anything about numKm as a property. How is it supposed to know that numKm corresponds to a particular CarProperty object?
The fundamental problem you're describing is key-value coding compliance. Cocoa's going to look for a method called numKm on the properties object. Not finding one, it'll try sending [properties valueForKey:#"numKm"]; Since valueForKey: doesn't know what to do with numKm, you get an error, but not before it calls [properties valueForUndefinedKey:#"numKm"]
But here's the catch: properties is an NSSet generated by Core Data, so you can't subclass it to override valueForUndefinedKey:. What you can do is create your own object that's KVC-compliant for your arbitrary properties and use that instead.
One solution is to subclass NSDictionary and make it act as a proxy. The primitive methods are count, objectForKey: and keyEnumerator. If you override these three methods, you can create an NSDictionary that's linked to your Car object and returns the appropriate CarProperty objects. For example:
#interface PropertyProxy : NSDictionary {
}
#property (nonatomic, readonly, assign) Car *car;
- (id)initWithCar:(Car *)car
#end
#implementation PropertyProxy
#synthesize car = _car;
- (id)initWithCar:(Car *)car {
if (!(self = [super init]))
return nil;
_car = car;
return self;
}
- (NSInteger)count {
return [car.properties count];
}
- (id)objectForKey:(NSString *)key {
return [[car.properties filteredSetUsingPredicate:[NSPredicate predicateWithFormt:#"key == %#", key]] anyObject];
}
- (NSEnumerator *)keyEnumerator {
return [[car valueForKeyPath:#"properties.key"] objectEnumerator];
}
#end
Then, in your Car class, do this:
#interface Car : NSManagedObject {
// other stuff
}
#property (nonatomic, readonly) NSDictionary *carProperties;
// other stuff
#end
#implementation Car
// other stuff
- (NSDictionary *)carProperties {
return [[[PropertyProxy alloc] initWithCar:self] autorelease];
}
#end
(Disclaimer: I just typed this into my web browser, so no guarantees this actually compiles :-))
As you can see, it's not the easiest thing in the world to do. You'll be able to set up key paths like this:
representedObject.carProperties.numKm;
Keep in mind that, while this is key-value coding compliant, it is not key-value observing compliant. So if numKm changes, you won't be able to observe that. You would need to do some extra work to make that happen.

Creating an array that contains only objects of a given class

Ok, so I have the code below (Objective-C FYI) and I was wondering if I want to create an NSMutableArray of c_data objects, how would I go about doing that? It's sort of like declaring a List<c_data> cData in C#.
#interface c_data : NSObject {
double value;
int label;
int ID;
}
#property double value;
#property int label;
#property int ID;
-(c_data*) init;
-(c_data*) initWithValue:(double)value;
#end
#implementation c_data
#synthesize value, label, ID;
-(c_data*) init {
return self;
}
-(c_data*) initWithValue:(double)val {
value = val;
return self;
}
#end
If you look at the class feat_data, I'm trying to make cData an array of the class c_data. I have included my attempts at it, but I don't think it's right because c_data isn't an array. Any suggestions?
#interface feat_data : NSObject {
NSMutableArray *nData;
NSMutableArray *cData;
char type;
}
#property(nonatomic, retain) NSMutableArray *nData;
#property(nonatomic, retain) NSMutableArray *cData;
#property char type;
-(feat_data*)init;
#end
#implementation feat_data
#synthesize nData, cData, type;
-(feat_data*)init {
nData = [[NSMutableArray alloc] init];
c_data *c_dataInstance = [[c_data alloc] init];
cData = [[NSMutableArray alloc] initWithArray:c_dataInstance];
return self;
}
#end
There is no such thing as statically typed / template / generic collections in Objective-C. Basically, the point of a strongly-typed collection is to provide static type safety at compile time. Such an approach makes little sense in a language as dynamic as Objective-C. The approach to the problem of disparate object types in Objective-C collections is to only insert the appropriate object type(s). (Also, remember that the array will retain objects it contains, so if you insert a new object without releasing and you lose the pointer to it, you're leaking memory.)
If you think about it, one of the biggest benefits to generics is being able to retrieve objects from the collection directly into a statically-typed variable without casting. In Objective-C, you can just store to an id variable and send whatever message you like without fretting about a ClassCastException, or the compiler complaining that an object doesn't (may not?) implement the method you're attempting to invoke. You can still statically type variables and cast results if desired, but the easier approach is to use dynamic typing (and -isKindOfClass: and -respondsToSelector: if necessary).
Incidentally, there are several related incarnations of this question on Stack Overflow. Knowing the term(s) to search for ("generic", "strongly-typed", or "template") can help find them. Here are a few:
Why do C# and VB have Generics? What benefit do they provide?
Is there any way to enforce typing on NSArray, NSMutableArray, etc.?
Is there anything like a generic list in Cocoa / Objective-C?
Are there strongly typed collections in Objective-C?
Finally, I agree with William — your init methods are pretty egregious in the sample you provided. You'd do well to learn and heed Apple's rules of Allocating and Initializing Objects in Objective-C. It requires breaking habits from other languages, but it will save you endless hours of insanity at some point down the road. :-)
[NSMutableArray addObject:[[[c_data alloc] init] autorelease]];
Objective-C arrays aren't typed. It seems you have some C++ unlearning to do.
On a related note, your inits are pretty bad. You need to call super init as well, as such:
- (id)init {
self = [super init];
if (self != nil) {
//Initialize here.
}
return self;
}
You would create an NSMutableArray and insert c_data objects into it.