Objective-C: writing a smart reusable compare function [duplicate] - objective-c

I'm trying to write an instance method for a Card class that compares a single card against an array. The class has some properties like: shape and color. The otherCards array is filled with other instances of this class, that also have their shapes and colors.
Now, I want to write a method that can check all of these attributes separately. How can I pass in a particular attribute, as in: [allAttributesIsEqual:otherCards compareWith: self.shape]? So I can pass in self.shape or self.color when actually comparing?
- (BOOL)allAttributesIsEqual: (NSArray *)otherCards
{
//self.shape is equal to othercards.shape
}

You can't just pass in self.shape, because that will give you the value of the property. Thanks to some of Cocoa/ObjC's dynamite, however, you can pass in the name of a property (or method) and get the results later.
The clever (dare I say, perhaps even "Pythonic") way:
// The name of the property we're interested in.
NSString * key = #"color";
// Get the values of that property for all the Cards in the array, then
// collapse duplicates, because they'll give the same results when comparing
// with the single card.
NSSet * vals = [NSSet setWithArray:[arrayOfCards valueForKey:key]];
// Now, if the set has only one member, and this member is the same
// as the appropriate value of the card we already have, all objects
// in the array have the same value for the property we're looking at.
BOOL colorIsEqual = ([vals count] == 1 && [vals containsObject:[myCard valueForKey:key]]);
Then your method can look like this:
- (BOOL)allOtherCards: (NSArray *)otherCards haveEqualAttribute: (NSString *)key;
Dan F's suggestion to implement - (BOOL)<#property#>Equal: (NSArray *)otherCards; for each property you're interested in is not at all a bad idea, however. Of course, each of these could call through to the base "clever" version.

The idea is that you (as the Card class) know what it means for two instances to be "equal". It sounds like in your case, two Cards are equivalent if their color and shape properties match. Start by implementing -isEqual: (along with -hash) on your custom Card class. This is the standard way of having an object expose a notion of whether it is the same as some other object. You can implement this however you need. Within this isEqual method, you can check all of the relevant properties:
- (BOOL)isEqual:(id)otherObject
{
if (![otherObject isKindOfClass:[self class]) {
return NO;
}
Card * otherCard = (Card *)otherObject;
// now compare the attributes that contribute to "equality"
return ([self.shape isEqual:otherCard.shape] && [self.color isEqual:otherCard.color]);
}
Now, once your custom object supports this -isEqual:, you can check all the cards in the array to see if any are equal to the candidate card. You could do the loop yourself and use the -isEqual:, but the nice thing about doing this in the system standard way is that you can also use system provided convenience methods to check for collection membership, like:
if ([myCardList containsObject:candidateCard]) {
// one of the cards compared as "equal"
}
If you would prefer to do this as you request in a method on your class, you could then structure it like so:
- (BOOL)isRepresentedInArray:(NSArray *)arr
{
return [arr containsObject:self];
}

Related

Set NSButton enabled based on NSArrayController selection

OK, so I've set up an NSTableView bound to NSArrayController.
Now, I also have an NSButton which I want to make it "enabled" when there is a selection, and disabled when there is nothing selected.
So, I'm bind NSButton's Enabled to the Array Controller's selection with a value transformer of NSIsNotNil.
However, it doesn't seem to be working.
Am I missing anything?
Regardless of whether or not anything is selected, the selection property of NSArrayController returns an object (_NSControllerProxyObject). This is why your binding isn't working the way you expect, because selection will never be nil. Instead, I bind to selectionIndexes, rather than selection, and have a value transformer called SelectionIndexesCountIsZero implemented like so:
#interface SelectionIndexesCountIsZero : NSValueTransformer
#end
#implementation SelectionIndexesCountIsZero
+ (Class)transformedValueClass { return [NSNumber class]; }
+ (BOOL)allowsReverseTransformation { return NO; }
- (id)transformedValue:(NSIndexSet *)value { return [NSNumber numberWithBool:[value count] > 0]; }
#end
Incidentally, you can still bind to selection if you wish, but it will require a custom value transformer. Apple state that: If a value requested from the selection proxy [object] using key-value coding returns multiple objects, the controller has no selection, or the proxy is not key-value coding compliant for the requested key, the appropriate marker is returned. In other words, to find out if there is in fact no selection, you need to (i) get access to the proxy object, (ii) call one of the methods of your actual objects, and (iii) test to see if the return value from (ii) is NSNoSelectionMarker. Doing it this way the key method of your value transformer would look like this:
- (id)transformedValue:(id)selectionProxyObject {
// Assume the objects in my table are Team objects, with a 'name' property
return [selectionProxyObject valueForKeyPath:#"name"] == NSNoSelectionMarker ? #YES : #NO;
}
selectionIndexes is the better way since it is completely generic. In fact, if you do this sort of thing a lot it can be a good idea to build up a transformer library, which you can then just import into any project. Here are the names of some of the transformers in my library:
SelectionIndexesCountIsZero
SelectionIndexesCountIsExactlyOne
SelectionIndexesCountIsOneOrGreater
SelectionIndexesCountIsGreaterThanOne
// ...you get the picture
I bind to selectedObjects.#count, rather than selection

Objective C - How to use inheritance to limit the types of classes entered into a method or array, and read those objects at a later time? [duplicate]

This question already has answers here:
Is there any way to enforce typing on NSArray, NSMutableArray, etc.?
(11 answers)
Closed 9 years ago.
How can I limit the type of objects put in an array if the limited objects are all inherited from a superclass?
So for instance, I have a parent class called
parentObj
I then have 3 child classes that have parameters that are added and not available to the superclass (parentObj)
childClass1
childClass2
childClass3
Then I have some other classes that are not related but I want to stay out of the array I'm trying to build up
otherClass1
otherClass2
I have this mutable array
NSMutableArray *arrayOfChildren;
that I want built up primarily of the three child classes of parentObj and not be able to contain the otherClasses
I don't want to build a different method to read and write the child classes to the array for each child class, because there could very well be more child classes that I build!
How can I have one method to add those classes to the array, and one to read them, including the child's added parameters?
The primary way I handle this problem was actually taught to me by a PHP book I was reading. Obviously PHP is not as strict as Objective C, so I had to do some changes.
I feel that this is a very useful skill to know how to do, especially for game programmers, where an array might need to carry hundreds of objects, all restricted to a certain type.
The nice thing about inheritance, is that the child classes also take on the "type" of their parent/ grandparent classes (and as far as you can go up if your tree of classes is long).
For example, if I had a method that had a parameter of type parentObj, you could put any of its children in that parameter slot and the code will run.
- (BOOL) addObjectToArray: (parentObj *) obj {
[array addObjectToArray:obj];
return true;
}
BOOL worked = [self addObjectToArray:childClass1];
[self addObjectToArray:childClass2];
[self addObjectToArray:childClass3];
[self addObjectToArray:otherClass1];
the code will run all the way up to the last line, in which it won't work properly. otherClass1 is not of type parentObj, so it won't work. This has successfully allowed us to limit the types of classes that the array can hold in one easy method!
Now reading the parameters from the objects is our next step to tackle. I'm sure there are other easier ways to do it, but this is how I do it.
I put a parameter in the super class (an integer) that will hold a so called ID for the child class.
parentObj.h:
//...
- (id) initWithProperties...:() :() :()... andID: (int)idType;
#property(nonatomic) int type;
//...
parentObj.m:
//...
- (id) initWithProperties...:() :() :()... andID: (int)idType {
//...
self.type = idType;
//...
}
//...
childClass1.h:
//...
#property(nonatomic) int someOtherPropertyOfChild1;
//...
childClass1.m:
//...
- (id) init {
self = [super initWithProperties... ...andID:1];
if (self) {
}
return self;
}
//...
childClass2.h:
//...
#property(nonatomic) int someOtherPropertyOfChild2;
//...
childClass2.m:
//...
- (id) init {
self = [super initWithProperties... ...andID:2];
if (self) {
}
return self;
}
//...
etc...
You need to remember which ID correlates to which child class, otherwise you are bound to get errors.
So now say you had a for loop that cycled through all the objects in the array full of classes. And say we needed to print out that extra parameter in each child class, how would we do that? I will show you how.
let's assume the variable being iterated in the for loop is x.
switch([array objectAtIndex:x].type) {
case 1:
//remember that childClass1 is id of one
childClass1 *CC1 = [array objectAtIndex:x];
NSLog(#"%d", CC1.someOtherPropertyOfChild1);
break;
//...
if the default case is called, that means that the object it is getting from the array is a parentObj object, or a child class that is not ID'd correctly.
I hope that this helps you in your troubles, and I hope it helps you understand why inheritance is important, and why you should use it!
Create a wrapper method that you use to add objects to the array:
- (void)addObject:(id)object
{
if ([object isKindOfClass:[parentObj class]])
{
[self.arrayOfChildren addObject:object];
}
}
You could also add an isMemberOfClass check if you wanted to also exclude instances of the parentObj class itself.

Comparing one property of an instance against an array of other instances

I'm trying to write an instance method for a Card class that compares a single card against an array. The class has some properties like: shape and color. The otherCards array is filled with other instances of this class, that also have their shapes and colors.
Now, I want to write a method that can check all of these attributes separately. How can I pass in a particular attribute, as in: [allAttributesIsEqual:otherCards compareWith: self.shape]? So I can pass in self.shape or self.color when actually comparing?
- (BOOL)allAttributesIsEqual: (NSArray *)otherCards
{
//self.shape is equal to othercards.shape
}
You can't just pass in self.shape, because that will give you the value of the property. Thanks to some of Cocoa/ObjC's dynamite, however, you can pass in the name of a property (or method) and get the results later.
The clever (dare I say, perhaps even "Pythonic") way:
// The name of the property we're interested in.
NSString * key = #"color";
// Get the values of that property for all the Cards in the array, then
// collapse duplicates, because they'll give the same results when comparing
// with the single card.
NSSet * vals = [NSSet setWithArray:[arrayOfCards valueForKey:key]];
// Now, if the set has only one member, and this member is the same
// as the appropriate value of the card we already have, all objects
// in the array have the same value for the property we're looking at.
BOOL colorIsEqual = ([vals count] == 1 && [vals containsObject:[myCard valueForKey:key]]);
Then your method can look like this:
- (BOOL)allOtherCards: (NSArray *)otherCards haveEqualAttribute: (NSString *)key;
Dan F's suggestion to implement - (BOOL)<#property#>Equal: (NSArray *)otherCards; for each property you're interested in is not at all a bad idea, however. Of course, each of these could call through to the base "clever" version.
The idea is that you (as the Card class) know what it means for two instances to be "equal". It sounds like in your case, two Cards are equivalent if their color and shape properties match. Start by implementing -isEqual: (along with -hash) on your custom Card class. This is the standard way of having an object expose a notion of whether it is the same as some other object. You can implement this however you need. Within this isEqual method, you can check all of the relevant properties:
- (BOOL)isEqual:(id)otherObject
{
if (![otherObject isKindOfClass:[self class]) {
return NO;
}
Card * otherCard = (Card *)otherObject;
// now compare the attributes that contribute to "equality"
return ([self.shape isEqual:otherCard.shape] && [self.color isEqual:otherCard.color]);
}
Now, once your custom object supports this -isEqual:, you can check all the cards in the array to see if any are equal to the candidate card. You could do the loop yourself and use the -isEqual:, but the nice thing about doing this in the system standard way is that you can also use system provided convenience methods to check for collection membership, like:
if ([myCardList containsObject:candidateCard]) {
// one of the cards compared as "equal"
}
If you would prefer to do this as you request in a method on your class, you could then structure it like so:
- (BOOL)isRepresentedInArray:(NSArray *)arr
{
return [arr containsObject:self];
}

Objective-C pattern for class instance variables?

What would be a nice pattern in Objective-C for class variables that can be "overridden" by subclasses?
Regular Class variables are usually simulated in Objective-C using a file-local static variables together with exposed accessors defined as Class methods.
However, this, as any Class variables, means the value is shared between the class and all its subclasses. Sometimes, it's interesting for the subclass to change the value for itself only. This is typically the case when Class variables are used for configuration.
Here is an example: in some iOS App, I have many objects of a given common abstract superclass (Annotation) that come in a number of concrete variations (subclasses). All annotations are represented graphically with a label, and the label color must reflect the specific kind (subclass) of its annotation. So all Foo annotations must have a green label, and all Bar annotations must have a blue label. Storing the label color in each instance would be wasteful (and in reality, perhaps impossible as I have many objects, and actual configuration data - common to each instance - is far larger than a single color).
At runtime, the user could decide that all Foo annotations now will have a red label. And so on.
Since in Objective-C, Classes are actual objects, this calls for storing the Foo label color in the Foo class object. But is that even possible? What would be a good pattern for this kind of things? Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.
Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.
Why do you think this would be ugly? It is a very simple approach since you can use [self className] as the key in the dictionary. It is also easy to make it persistent since you can simply store the dictionary in NSUserDefaults (as long as it contains only property-list objects). You could also have each class default to its superclass's values by calling the superclass method until you find a class with a value.
+ (id)classConfigurationForKey:(NSString *)key {
if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
Class c = [self class];
id value = nil;
while(value == nil) {
NSDictionary *classConfig = [_configurationDict objectForKey:[c className]];
if(classConfig) {
value = [classConfig objectForKey:key];
}
c = [c superclass];
}
return value;
}
+ (void)setClassConfiguration:(id)value forKey:(NSString *)key {
if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
NSMutableDictionary *classConfig = [_configurationDict objectForKey:[self className]];
if(classConfig == nil) {
classConfig = [NSMutableDictionary dictionary];
[_configurationDict setObject:classConfig forKey:[self className]];
}
[classConfig setObject:value forKey:key];
}
This implementation provides no checking to make sure you don't go over the top superclass, so you will need to ensure that there is a value for that class to avoid an infinite loop.
If you want to store objects which can't be stored in a property list, you can use a method to convert back and forth when you access the dictionary. Here is an example for accessing the labelColor property, which is a UIColor object.
+ (UIColor *)classLabelColor {
NSData *data = [self classConfigurationForKey:#"labelColor"];
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
+ (void)setClassLabelColor:(UIColor *)color {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:color];
[self setClassConfiguration:data forKey:#"labelColor"];
}
my answer here may help:
What is the recommended method of styling an iOS app?
in that case, your annotation just holds a reference to a style (e.g. you need only one per style), and the size of a pointer for an entire style is not bad. either way, that post may give you some ideas.
Update
Jean-Denis Muys: That addresses the sample use case of my question, but not my question itself (a pattern to simulate class instance variables).
you're right, i didn't know how closely your example modeled your problem and i considered commenting on that.
for a more general and reusable solution, i'd probably just write a threadsafe global dictionary if your global data is nontrivial (as you mentioned in your OP). you could either populate it in +initialize or lazily by introducing a class method. then you could add a few categories to NSObject to access and mutate the static data -- do this for syntactical ease.
i suppose the good thing about that approach is that you can reuse it in any program (even though it may appear ugly or complex to write). if that's too much locking, then you may want to divide dictionaries by prefixes or create a simple thread safe dictionary which your class holds a reference to -- you can then synthesize an instance variable via the objc runtime to store it and declare an instance method to access it. the class method would still have to use the global data interface directly.

Comparing objects in Obj-C

How does one compare objects in Objective-C?
Is it as simple as == ?
I want to check an array for an object and if it doesnt exist add it to the array otherwise, remove it from the array.
Comparing objects in Objective-C works much the same as in Java or other object-oriented languages:
== compares the object reference; in Objective-C, whether they occupy the same memory address.
isEqual:, a method defined on NSObject, checks whether two objects are "the same." You can override this method to provide your own equality checking for your objects.
So generally to do what you want, you would do:
if(![myArray containsObject:anObject]) {
[myArray addObject:anObject];
}
This works because the Objective-C array type, NSArray, has a method called containsObject: which sends the isEqual: message to every object it contains with your object as the argument. It does not use == unless the implementation of isEqual: relies on ==.
If you're working entirely with objects that you implement, remember you can override isEqual: to provide your own equality checking. Usually this is done by comparing fields of your objects.
Every Objective-C object has a method called isEqual:.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/isEqual:
So you would want to override this for your custom object types.
One particular important note in the documentation:
If two objects are equal, they must
have the same hash value. This last
point is particularly important if you
define isEqual: in a subclass and
intend to put instances of that
subclass into a collection. Make sure
you also define hash in your subclass.
== will compare the pointer, you need to override
- (BOOL)isEqual:(id)anObject
Implement isEqual: and hash
Per the Apple documentation on NSObject you need to implement isEqual: and hash at a minimum. Below you'll find one way to implement object equality, of course how to implement hash enters the land of serious debate here on StackOverflow, but this will work. General rule - you need to define what constitutes object equality and for each unique object they should have a unique hash. It is best practice to add an object specific equality method as well, for example NSString has isEqualToString:.
- (BOOL)isEqual:(id)object
{
BOOL result = NO;
if ([object isKindOfClass:[self class]]) {
CLPObject *otherObject = object;
result = [self.name isEqualToString:[otherObject name]] &&
[self.shortName isEqualToString:[otherObject shortName]] &&
[self.identifier isEqualToString:[otherObject identifier]] &&
self.boardingAllowed == [otherObject isBoardingAllowed];
}
return result;
}
- (NSUInteger)hash
{
NSUInteger result = 1;
NSUInteger prime = 31;
result = prime * result + [_name hash];
result = prime * result + [_shortName hash];
result = prime * result + [_identifier hash];
result = prime * result + _boardingAllowed;
return result;
}