struggling with correct way to name this method - iphone

Long time c#/java/c++ programmer, brand spankin new to objectivce C. Read the guidelines, looked api's, but not quite there yet on the naming convention.
Example: assume I have a Cars class that has an array of every car, and you wanted methods to return a subset of the array.
I see NSArray has a method: getObjects, but in most cases I don't see the "get". So
what do you prefer?
All inputs appreciated! Spent way too much time thinking about this.
Option A) -(NSArray *) getCarsWithColor:(NSString *)color;
Option B) -(NSArray *) getCars:(NSString *)withColor;
Optoin C) -(NSArray *) carsWithColor:(NSString *)color;
OPtion D) -(NSArray *) cars:(NSString *)withColor;
Option E) none of the above, name it xxxxxxxxxxxx....
Thanks.

Objective-C methods are seldom named with get. The getObjects: method has get in it only because the result is placed in a buffer in an input argument.
-(void)getObjects:(id*)aBuffer;
^^^^ ^^^^^^^
whereas your method is not filling a buffer, but return an array. Option (A) and (B) are out.
Also, the kind of argument is usually part of the selector name (stuff before :), e.g.
-(UIView*)viewWithTag:(NSInteger)tag
^^^^^^^
// not view:(NSInteger)withTag
-(CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view
// not convert:(CGPoint)point from:(UIView*)view;
so option (D) is discouraged.
A detailed guideline for naming methods can be found in Coding Guidelines for Cocoa: Naming Methods. This guideline also include other conventions which you may be interested.

Option C is the best. Never use "get" unless you're getting pointers into a C array and the arguments should only be named for the method signature that refers to them.
This way, longer methods with multiple arguments are clearer:
-(NSArray *)carsWithColor:(NSColor *)color
wheels:(NSInteger)wheels
seats:(NSInteger)seats
premiumInterior:(BOOL)premiumInterior ...
...which can be shortened to: -carsWithColor:wheels:seats:premiumInterior:... when describing it to others.

"C" is the standard way to do it. get is very rarely used in getters and the more verbose carsWithColor is preferred.

In addition to what everyone else has said, I'd be curious why you're storing an NSArray of objects in the Cars class. It sounds to me like cars is an NS[Mutable]Array ivar somewhere containing instances of the car class. Then, you don't need this method at all. If you're using Core Data, then you'd do a fetch and if you're just handling the NSArray yourself, you could use a predicate to filter the array's objects. I think that is the bit that strikes me as the most un-Cocoa aspect of your question. If you do need this method, then it would be defined on the object containing the NSArray ivar like:
NSArray *cars = [NSArray arrayWithObjects:car1, car2, car3, nil];
(NSArray *)carsWithColor:(NSString *)color{
return [cars filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"color == %#", color]];
}
That code is untested, but it's how I would approach the problem. The method is really a controller type method and shouldn't be part of your model logic. Having the Cars class sounds like muddled MVC to me.

Objective-C method names do not use the get prefix. So option C is closest to being correct, with the caveat that the aWithB construction implies the NSArray * that comes back will be autorelease-d.

Oh, there are so many ways to do what you're trying to do! Usually, the "get..." notation should be avoided unless you're defining a custom getter, to avoid confusion. I vote for option C, "-(NSArray *) carsWithColor:(NSString *)color;".

Related

Woes in trapping valueForKeyPath: without a valid path

I have a nested NSMutableDictionary and am successfully pulling out a value at some 'depth' nested in other dictionaries, like:
NSNumber *num = [myDictionary valueForKeyPath:#"league.team.away.score"];
All is good. And I can confirm that all dictionaries at all levels are mutable.
But... what if that key path does not exist?
As expected, I get an NSUndefinedKeyException. I tried a fix with a try/catch tactic, to no avail.
Apple's solution to this concerns overriding valueForUndefinedKey:
"Subclasses can override this method to return an alternate value for undefined keys. The default implementation raises an NSUndefinedKeyException."
Great... so I create a subclass NSMutableDictionaryMod, then I get a complaint...
[NSMutableDictionary initWithCapacity:] method only defined for abstract class. Define -[NSMutableDictionaryMod initWithCapacity:]!
I go define said initializer (and can confirm it gets there in Xcode), but boom it crashes on the self = [super initWithCapacity:numItems]; line.
Then I noticed this Apple gem on NSMutableDictionary:
There should typically be little need to subclass NSMutableDictionary. If you do need to customize behavior, it is often better to consider composition rather than subclassing.
Any suggestions would be appreciated.

CoreData Object typing won't work

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"];
}

Sorting objects by the return value of a method

I recently encountered some problems while trying to sort a set of objects.
The objects I'd like to sort are subclasses of NSManagedObject.
I want to sort the objects by a 'global_index', which is, however, not a property in my model. It's just a getter -(NSInteger)globalIndex {...} each of the objects implements. Inside this method, I do some complex calculation which cannot be done with a simple sort descriptor.
Now, my question: Is there a way to make an NSSortDescriptor sort the objects by the return value of a method?
I really want to use a sort descriptor because it's (IMO) the only way to make use of NSFetchedResultsController's cool features. Or is there a way to tell the controller how to sort the fetched objects? Like...
- (NSArray *)sortObjects:(NSSet *)objects {...}
Thanks in advance!
According to the Core Data Programming Guide,
The SQL store, on the other hand, compiles the predicate and sort descriptors to SQL and evaluates the result in the database itself. This is done primarily for performance, but it means that evaluation happens in a non-Cocoa environment, and so sort descriptors (or predicates) that rely on Cocoa cannot work. The supported sort selectors are compare: and caseInsensitiveCompare:, localizedCompare:, localizedCaseInsensitiveCompare:, and localizedStandardCompare: (the latter is Finder-like sorting, and what most people should use most of the time). In addition you cannot sort on transient properties using the SQLite store.
The easy workaround is to save the order with each object (which is a pain when you need to insert an object between two others, but that's a problem with trying to implement an efficient ordered collection in a database).
Have you tried simply passing #"globalIndex" as the sort descriptor's key? That should work fine since there's an accessor for that key.
You should be able to 'trick' NSSortDescriptor into using your global index method like it would any regular accessor method (the accessor should conform to the expectations of Key-Value Programming). In other words, treat globalIndex as a property of your NSManagedObject subclasses and have the following methods in each one of them.
-(NSInteger) globalIndex {
...
}
-(void) setGlobalIndex: (NSInteger) idx {
...
}
To make things even easier, you can define a subclass of NSManagedObject with your extra methods and extend your subclasses from that (or create a Category, either way). Should work just fine.

Most efficient way to find out if an object is already in an NSMutableArray?

I just want to know if an object is in an array or not.
So I can use:
- (BOOL)containsObject:(id)anObj
But it would send -isEqual to every object in the array. Bad when there are thousands of them.
And then there is:
- (NSUInteger)indexOfObjectIdenticalTo:(id)anObject
which seems to only compare the memory addresses. I think this is faster. But a bit nasty to use, of course. One would have to check for NSNotFound.
Is -indexOfObjectIdenticalTo really the best option?
if you really need this often, you can create an instance method by category:
#interface NSArray (MONStuff)
- (BOOL)mon_containsObject:(id)object;
#end
#implementation NSArray (MONStuff)
- (BOOL)mon_containsObject:(id)object {
return NSNotFound != [self indexOfObjectIdenticalTo:arg];
}
#end
or these's also CFArrayContainsValue.
a simple function would also suffice.
But a bit nasty to use
Why? It seems to me that
if ([array indexOfObjectIdenticalTo: foo] != NSNotFound)
{
// do what you need
}
is not much more nasty than
if ([array containsObject: foo])
{
// do what you need
}
Which one you choose depends on what equality semantics you use. You almost certainly want to use -containsObject: for arrays containing NSStrings or NSNumbers because -isEqual: gives the correct equality semantics.
Both methods, by the way are O(n) which is where the real performance problem is. If the idea of a linear search is a problem, consider a different data structure e.g. based on NSDictionary.
As per your explaining and comparison indexOfObjectIdenticalTo seems me the first choice to use..
Here is one more SO post ..
indexOfObject vs. indexOfObjectIdenticalTo
If possible (for example if sorting order is irrelevant) you could use an NSDictionary instead, with your object as keys and values of [NSNull null]. Note that the objects get copied when used as keys ! Your objects would need to implement the - (NSUInteger)hash method.
Also see the excellent NSArray or NSSet, NSDictionary or NSMapTable analysis from Matt Gallagher.

Why are these class methods needed when there are perfectly fine parent class methods?

Why are class methods such as
+ (NSMutableArray *)array;
needed when there are perfectly fine parent class methods such as
+ arrayWithObjects:(id)firstObj, ...;
which could be set to
[array arrayWithObjects:nil]
and have the same effect.
Are they actually not equivalent things?
They're convenience methods, to make it easier to just get an autoreleased object fast instead of having to write a lot more to get the same result.
BTW, NSArray does not have an instance method called arrayWithObjects, they are only class methods.
Jacob's answer is right (so accept that one) but I'll add that the Cocoa framework has a spiffy thing under the hood called class clusters. The idea is that, although you may call [NSArray array], the object you get back is actually a private subclass of NSArray that's optimized for your specific situation. These convenience methods can give the NSArray class a "hint" as to which class to use.
For example, if you call [NSArray array], you get an empty, immutable array. Well, how many different values can an empty immutable array have? Just one. So behind the scenes, Cocoa can return the same empty NSArray to every call to [NSArray array] so that it only ever has to allocate one of these. This is a nifty optimization that saves some memory.
Now, this is really an implementation detail you don't need to concern yourself with, but the takeaway is that you should use the constructor that most closely matches the result you want, especially with collection classes like arrays, sets, and dictionaries, because Apple's implemented a boatload of optimizations that make your application work better.