How to loop back the attribute in an NSObject? - objective-c

For example the Object is something like this:
MyUser: NSObject{
NSString *firstName;
NSString *lastName;
NSString *gender;
int age;
}
and I would like to compare to user, if their attributes are the same, I will treat it as equal... instead of write a static method to compare enough attribute one by one, can I have a lazy way to get all the attribute to compare themselves, Thanks.?

For comparison, this is what you're trying to avoid writing.
-(NSUInteger)hash {
return [firstName hash] ^ [lastName hash] ^ [gender hash] ^ age;
}
-(BOOL)isEqual:(id)other {
return [other isKindOfClass:[self class]]
&& age == other.age
&& [gender isEqualToString:other.gender]
&& [firstName isEqualToString:other.firstName]
&& [lastName isEqualToString:other.lastName];
}
Using XOR is an extremely simple way of combining hashes, and I mostly include it as a stand-in. It may hurt the quality of the hash value, depending on distribution of the underlying hash functions. If the hashes have a uniform distribution, it should be all right. Note also that combining hashes only works because NSStrings that are equal in content have the same hashes. This approach won't work with all types; in particular, it won't work with types that use the default implementation of hash.
To get around writing the above, first change the type of the age property to NSNumber, so it doesn't have to be handled as a special case. You don't have to change the ivar, though you can if you want.
#interface MyUser : NSObject {
...
unsigned int age; // Or just make this an NSNumber*
}
...
#property (assign,nonatomic) NSNumber *age;
#implementation MyUser
#synthesize firstName, lastName, gender;
/* if the age ivar is an NSNumber*, the age property can be synthesized
instead of explicitly defining accessors.
*/
#dynamic age;
-(NSNumber*)age {
return [NSNumber numberWithUnsignedInt:age];
}
-(void)setAge:(NSNumber*)newAge {
age = [newAge unsignedIntValue];
}
Second, make sure your class supports the fast enumeration protocol. If it doesn't, you can implement -countByEnumeratingWithState:objects:count: by making use of reflection (with the Objective-C runtime functions) to get the list of properties for instances of your class. For example (taken in part from "Implementing countByEnumeratingWithState:objects:count:" on Cocoa With Love):
#import <objc/runtime.h>
...
#interface MyUser (NSFastEnumeration) <NSFastEnumeration>
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len;
#end
#implementation MyUser
#synthesize firstName, lastName, gender;
/* defined in the main implementation rather than a category, since there
can be only one +[MyUser initialize].
*/
static NSString **propertyNames=0;
static unsigned int cProperties=0;
+(void)initialize {
unsigned int i;
const char *propertyName;
objc_property_t *properties = class_copyPropertyList([self class], &cProperties);
if ((propertyNames = malloc(cProperties * sizeof(*propertyNames)))) {
for (i=0; i < cProperties; ++i) {
propertyName = property_getName(properties[i]);
propertyNames[i] = [[NSString alloc]
initWithCString:propertyName
encoding:NSASCIIStringEncoding];
}
} else {
cProperties = 0;
// Can't initialize property names. Fast enumeration won't work. What do?
}
}
...
#end
#implementation MyUser (NSFastEnumeration)
-(NSUInteger)
countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id *)stackbuf
count:(NSUInteger)len
{
if (state->state >= cProperties) {
return 0;
}
state->itemsPtr = propertyNames;
state->state = cProperties;
state->mutationsPtr = (unsigned long *)self;
return cProperties;
}
#end
Last, implement hash (using fast enumeration) and isEqual:. Hash should calculate the hashes of all properties, then combine them to create the hash for the MyUser instance. isEqual: can simply check the other object is an instance of MyUser (or a subclass thereof) and compare hashes. For example:
-(NSUInteger)hash {
NSUInteger myHash=0;
for (NSString *property in self) {
// Note: extremely simple way of combining hashes. Will likely lead
// to bugs
myHash ^= [[self valueForKey:property] hash];
}
return myHash;
}
-(BOOL)isEqual:(id)other {
return [other isKindOfClass:[self class]]
&& [self hash] == [other hash];
}
Now, ask yourself which is less work overall. If you want a single approach what will work for all your classes, it might be the second (with some changes, such as turning +initialize into a class method on NSObject that returns the property name array and length), but in all likelihood the former is the winner.
There's a danger in both of the above hash implementations with calculating the hash based on property values. From Apple's documentation on hash:
If a mutable object is added to a collection that uses hash values to determine the object’s position in the collection, the value returned by the hash method of the object must not change while the object is in the collection. Therefore, either the hash method must not rely on any of the object’s internal state information or you must make sure the object’s internal state information does not change while the object is in the collection.
Since you want isEqual: to be true whenever two objects have the same property values, the hashing scheme must depend directly or indirectly on the object's state, so there's no getting around this danger.

Related

NSSet containing objects with overwritten isEqual

In my class i overwrite the isEqual
#interface MyClass : NSObject
#property (nonatomic, strong) NSString * customID;
#end
I overwrite the isEqual so it checks only the equality of customID
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[MyClass class]]) {
if (self.customID == nil) {
return NO;
}
return [self.customID isEqual:[object customID]];
}
return [super isEqual:object];
}
Now the NSSet is practically a hash table, making it fast to check, if it contains a hash value... thats something we know
but, let imagine this code
NSArray * instancesToCheck = ...;
NSArray * allInstances = ...;
for (MyClass * instance in allInstances) {
if ([instancesToCheck containsObject:instance]) {
// do smth
}
}
i would like to "optimize" with this one (use a NSSet for membership testing)
NSArray * instancesToCheck = ...;
NSArray * allInstances = ...;
NSSet * instancesToCheckAsSet = [NSSet setWithArray:instancesToCheck];
for (MyClass * instance in allInstances) {
if ([instancesToCheckAsSet containsObject:instance]) {
// do smth
}
}
Does the second code provide any performance benefit at all (under the assumption, that there were no duplicates in the array from which it was created, and the instancesToCheck contains different pointers, but some of the objects have the same customID, making isEqual==YES but pointer comparison==NO)?
When i looked up the docs, i found out, that the containsObject calls the isEqual, so it has to iterate over all objects anyway
What are the performance implications when using NSSet with objects, that overwrite isEqual? Becomes the NSSet less effective then?
Does the second code provide any performance benefit at all
Absolutely. An array must cycle through the array examining every object. A set knows more or less instantly whether an object is contained, because it is a hash table. Indeed, this sort of thing is exactly what a set is for.
You MUST overwrite hash, if you overwrite isEqual: doing otherwise might break the functionality and things might not behave as expected
Two objects that are considered "equal" must return the same hash value.
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[MyClass class]]) {
if (self.customID == nil) {
return NO;
}
return [self.customID isEqual:[object customID]];
}
return [super isEqual:object];
}
// MUST overwrite hash
- (NSUInteger)hash {
return [self.customID hash];
}

Objective-C NSArray remove object duplicates based on function

It is clear from this question that there are many ways to remove duplicates from an NSArray when the array's elements are primitive types, or when the elements are perfect duplicates. But, is there a way to remove duplicates based on a transformation applied to each element, as is permitted in Underscore.js's uniq function, rather than by simply comparing the whole elements? And if a manual implementation would be difficult to optimize, is there an efficient system-provided method (or 3rd party library algorithm) for accomplishing this that I am missing?
A simple approach:
NSMutableArray* someArray = something;
for (int i = someArray.count - 1; i > 0; i--) {
MyObject* myObject = someArray[i];
for (int j = 0; j < i; j++) {
MyObject* myOtherObject = someArray[j];
if ([myObject isSortaEqual:myOtherObject]) {
[someArray removeObjectAtIndex:i];
break;
}
}
}
Yes, it's N-squared, but that's not a biggie unless the array is fairly large.
If you want to redefine what equality means for your objects, then consider overriding -hash and -isEqual:. Then you can create an NSSet from your array if order is irrelevant, or an NSOrderedSet if it is relevant. Here's an example of a Person class where I want the name of the person to determine object equality.
#interface Person
#property (nonatomic, copy) NSString *name;
#end
#implementation Person
- (BOOL)isEqual:(id)object
{
Person *otherPerson = (Person *)object;
return [self.name isEqualToString:otherPerson.name];
}
- (NSUInteger)hash
{
return [self.name hash];
}
#end
Uniquing them now is rather easy:
NSArray *people = ...;
// If ordered is irrelevant, use an NSSet
NSSet *uniquePeople = [NSSet setWithArray:people];
// Otherwise use an NSOrderedSet
NSOrderedSet *uniquePeople = [NSOrderedSet orderedSetWithArray:people];
Absolutely. You are looking for a way to pass your own method for testing for uniqueness (at least, that's what the uniq function you refer to does).
indexesOfObjectsPassingTest: will allow you to pass your own block to determine uniqueness. The result will be an NSIndexSet of all the objects in the array that matched your test. With that you can derive a new array. The block you are passing is roughly equivalent to the Underscore iterator passed to uniq.
The sister method, indexesOfObjectsWithOptions:passingTest: also allows you to specify enumeration options (i.e. concurrent, reverse order, etc.).
As you mention in your question, there are lots of ways to accomplish this. NSExpressions with blocks, Key-value coding collections operators, etc. could be used for this as well. indexesOfObjectsPassingTest: is probably the closest to what you seem to be looking for, though you can do much the same thing (with a lot more typing) using expressions.
I just came up against this problem, so I wrote a category on NSArray:
#interface NSArray (RemovingDuplicates)
- (NSArray *)arrayByRemovingDuplicatesAccordingToKey:(id (^)(id obj))keyBlock;
#end
#implementation NSArray (RemovingDuplicates)
- (NSArray *)arrayByRemovingDuplicatesAccordingToKey:(id (^)(id obj))keyBlock
{
NSMutableDictionary *temp = [NSMutableDictionary dictionaryWithCapacity:[self count]];
for (NSString *item in self) {
temp[keyBlock(item)] = item;
}
return [temp allValues];
}
#end
You can use it like this (this example removes duplicate words, ignoring case):
NSArray *someArray = #[ #"dave", #"Dave", #"Bob", #"shona", #"bob", #"dave", #"jim" ];
NSLog(#"result: %#", [someArray arrayByRemovingDuplicatesAccordingToKey:^(id obj){
return [obj lowercaseString];
}]);
Output:
2015-02-17 17:44:10.268 Untitled[4043:7711273] result: (
dave,
shona,
jim,
bob
)
The 'key' is a block that returns an identifier used to compare the objects. So if you wanted to remove Person objects according to their name, you'd pass ^(id obj){ return [obj name]; }.
This solution is O(n), so is suitable to large arrays, but doesn't preserve order.

How to check a typedef'd obj in Objective-c NSDictionary

I've got an method that takes NSDictionary arg. This NSDictionary has some predefined keys it'll take. All the obj's should be strings. But only certain string objs are valid for each key.
So my approach was to typedef NSString for each valid string per key. I'm hoping not to extend the NSString class.
I've typedef'd some NSString's...
typedef NSString MyStringType
Then I define a few...
MyStringType * const ValidString = #"aValidString";
Here's what I'd like to do in my sample method..
- (void)setAttrbiutes:(NSDictionary *)attributes {
NSArray *keys = [attributes allKeys];
for (NSString *key in keys) {
if ([key isEqualToString:#"ValidKey"]) {
id obj = [attributes objectForKey:key];
//Here's where I'd like to check..
if (**obj is MyStringType**) {
}
}
}
}
I'm open to other ideas if there's a better approach to solve the obj type problem of an NSDictionary.
Doesn't work like that; typedefs are a compile time alias that don't survive being passed through a dictionary.
In any case, using typedefs for something like this would be unwieldy.
I suggest you create a property list -- either as a file in your project or in code -- that contains the specifications of your various keys and valid values, then write a little validator that, passed a string and value, can validate the string-value pair for validity.
This also gives you the flexibility to extend your validator in the future. For example, you might have a #"Duration" key that can only be in the range of 1 to 20.
Instead of setting up a typedef for you special values, one possible option would be to create an NSSet of the special values. Then in your code you can verify that the object in the dictionary is in your set.
What about a combination of category on NSString + associated object?
Something along the lines (untested!!):
#interface NSString (BBumSpecial)
- (NSString *) setSpecial: (BOOL) special ;
- (BOOL) special ;
#end
and:
#implementation NSString (BBumSpecial)
static void * key ;
- (NSString *) setSpecial: (BOOL) special {
objc_setAssociatedObject(self, &key, special ? #YES : #NO, OBJC_ASSOCIATION_ASSIGN) ;
return self ;
}
- (BOOL) special {
id obj = objc_getAssociatedObject(self, &key) ;
return obj && [obj boolValue] ;
}
#end
Which you could then use as:
NSString * mySpecialString = [#"I'm Special" setSpecial:YES] ;
?

Objective-C accessing float getters with variable names

Let's say I have an NSArray called myArray of NSStrings (#"a0",#"a1",#"a2")
Then in a fast enumeration I loop into my array to build properties according to that NSStrings. I've got a problem accessing that properties.
I'm trying something like that :
#property (nonatomic) float a0propertyLow;
#property (nonatomic) float a0propertyHigh;
#property (nonatomic) float a1propertyLow;
#property (nonatomic) float a1propertyHigh;
..
.. etc.
for (NSString *aPos in myArray) {
NSString *low = [NSString stringWithFormat:#"%#propertyLow",aPos];
NSString *high = [NSString stringWithFormat:#"%#propertyHigh",aPos];
SEL lowSel = NSSelectorFromString(low);
SEL highSel = NSSelectorFromString(high);
if ([self respondsToSelector:lowSel]&&[self respondsToSelector:highSel]) {
id sumPartOne = [self performSelector:lowSel];
id sumPartTwo = [self performSelector:highSel];
float bla = (float)sumPartOne + (float)sumPartTwo;
}
}
I know my code is wrong but I don't know how to make it work.
My problem is that lowSel and highSel are getters which returns float but the perform selector method returns id which is ok for an object but not for floats.
So, how can I access my float getters with variable names ? I'm sure answer must be simple but it seems that my mind is looking for something complicated (and which obviously doesn't work) so I'm asking for help :)
Thank you very much for your help
You can't use performSelector: to call a method that returns a scalar value. The documentation for performSelector: clearly says what you have to do:
For methods that return anything other than an object, use NSInvocation.
An NSInvocation is a little more complex to set up but more flexible regarding arguments and return types.
In your case, it is probably easier to use Key-Value Coding instead:
[self valueForKey:low];
takes the return type into account and will automatically wrap the float in an NSNumber.
If you really need to use these getter methods, you can change your properties to double and use objc_msgSend_fpret():
#include <objc/runtime.h>
#include <objc/message.h>
double arg0 = objc_msgSend_fpret(self, lowSel);
If you can avoid getters (I know, that's not good practice, but anyway, it works for sure with the following method), and use the instance variables directly:
void *object_getIvarPtr(id obj, const char *name)
{
if (!obj || !name)
{
return NULL;
}
Ivar ivar = object_getInstanceVariable(obj, name, NULL);
if (!ivar)
{
return NULL;
}
return ((char *)obj + ivar_getOffset(ivar));
}
float arg0 = *(float *)object_getIvarPtr(self, [lowSel UTF8String]);
Hope this helps.
One way you can do is convert your floats into objects at runtime such as:-
NSString *str=[NSSTring stringWithFormat:#"%f",yourFloatValue];
and then u can retrive it using
[str floatValue];

Get property name as a string

I need a way to pass a property and get the name assigned to it. Any suggestions?
#property (nonatomic, retain) MyObject *crazyObject;
NSString *str = SOME_WAY_TO_GET_PROPERTY_NAME(crazyObject);
// Above method should return #"crazyObject"
You can try this:
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([self class], &propertyCount);
NSMutableArray * propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char * name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
NSLog(#"Names: %#", propertyNames);
It's as simple as this...expanding upon what Chuck already mentioned:
#ifndef STR_PROP
#define STR_PROP( prop ) NSStringFromSelector(#selector(prop))
#endif
You then use it like so:
NSString *strProp = STR_PROP(myProperty);
Background
Keep in mind that properties are really just, to quote Apple, "a syntactical shorthand for declaring a class’s accessor methods." In fact, by itself, the #property declaration doesn't even work. Your #synthesize statement translates the #property into the equivalent of two methods:
- (void)setCrazyObject:(MyObject *)something;
- (MyObject *)crazyObject;
Which one is used depends on the context surrounding your self.crazyObject. (#synthesize also creates a matching instance variable if you didn't do it yourself.) The offshoot of all this is that you can't really translate to and from a property with one single method.
Proposed Solution
You can use what Apple already provides:
NSString *foo = NSStringFromSelector(#selector(myClassProperty));
Or do something custom:
Given that self.crazyObject really translates to either [self crazyObject] or [self setCrazyObject:foo] by the time your code is running, ou'll probably need two methods, like:
- (NSString *)setterStringForProperty:(SEL)prop;
- (NSString *)getterStringForProperty:(SEL)prop;
You might then want at least 2 companion methods such as:
- (SEL)setterForPropertyName:(NSString *)propString;
- (SEL)getterForPropertyName:(NSString *)propString;
Within these methods, you can use the Foundation functions NSStringFromSelector and NSSelectorFromString to convert back and forth between SEL and NSString. Use whatever string manipulations you like to convert back and forth between your setter string (setCrazyObject) and your property name (crazyObject).
A complete solution is hard to provide without knowing the exact use case, but hopefully this provides some more clues for anyone trying to accomplish something similar. There might even be some useful things made possible by combining this approach with Oscar's answer.
Here is a function that returns the name of an ivar, so basically it not only returns the properties but any ivar of the class. I haven't found a way to get the property directly so I used the ivar trick.
#import <objc/objc.h>
/// -----
- (NSString *)nameOfIvar:(id)ivarPtr
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
id pointer = object_getIvar(self, ivar);
if(pointer == ivarPtr)
{
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
break;
}
}
free(ivars);
}
return name;
}
After searching and debugging i find solution for me...
Added #import <objc/runtime.h>
Methods object_getIvar(id obj, Ivar ivar) send bad access and app crashes. i modify some code and it worked great:
+(NSString*)stringWithProperty:(id)property withClass:(id)controller
{
NSString *name = nil;
uint32_t ivarCount;
Ivar *ivars = class_copyIvarList([controller class], &ivarCount);
if(ivars)
{
for(uint32_t i=0; i<ivarCount; i++)
{
Ivar ivar = ivars[i];
name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([controller valueForKey:name] == property)
{
break;
}
}
free(ivars);
}
return name;
}
Modifying the solution, it works when your object is allocated already, otherwise it returns nil:-
NSString * NSStringFromProperty(NSObject* property, NSObject* class)
{
unsigned int propertyCount = 0;
objc_property_t * properties = class_copyPropertyList([class class], &propertyCount);
NSString *name = nil;
for (unsigned int i = 0; i < propertyCount; ++i)
{
name = [NSString stringWithUTF8String:property_getName(properties[i])];
NSObject *object = [class valueForKey:name];
if (object != nil && object == property)
{
break;
}
else
{
name = nil;
}
}
free(properties);
return name;
}
You can use
NSString *str = NSStringFromSelector(#selector(crazyObject));
The good thing about this approach is that:
Xcode will autocomplete word crazyObject for you.
When later on you will change the property name from crazyObject to myCrazyObject, Xcode will add a warning saying "unrecognized selector!" -- pretty good for debugging.
I use this method so often, that I even created a function, which allows to write less letters:
NSString * __nonnull sfs(SEL __nonnull theSelector)
{
if (!theSelector)
{
abort();
}
return NSStringFromSelector(theSelector);
}
Now your final solution can look like this:
NSString *str = sfs(#selector(crazyObject));
From Get property name as string, without using the runtime reference library, just define:
#define propertyKeyPath(property) (#""#property)
#define propertyKeyPathLastComponent(property) [[(#""#property) componentsSeparatedByString:#"."] lastObject]
And then you can do something like this:
NSLog(#"%#", propertyKeyPathLastComponent(appleStore.storeLocation.street)); //result: street
You may check my approach at Gist to get the string for a property with autocompletion and compile-time check.
How to use:
Get the property name for a class:
#interface AnyClass : NSObject
#property (strong) NSData *data;
#end
// == My approach ==
// C string for a class
PropertyNameForClass(AnyClass, data); // ==> "data"
// NSString for a class
PropertyStringForClass(AnyClass, data); // ==> #"data"
// Bad approach (no autocompletion; no compile-time check):
NSString *propertyName = #"data";
Get the property name for a protocol:
#protocol AnyProtocol
#property (strong) NSDate *date;
#end
// C string for a protocol
PropertyNameForProtocol(AnyProtocol, date); // ==> "date"
// NSString for a protocol
PropertyStringForProtocol(AnyProtocol, date); // ==> #"date"
Unconventional, hacky, ugly, late, but... as strong-named as it gets and works like a charm:
#define SOME_WAY_TO_GET_PROPERTY_NAME(p) p == p ? [[[[[[[NSString alloc] initWithCString:#p encoding:NSUTF8StringEncoding] componentsSeparatedByString:#"."] lastObject] componentsSeparatedByString:#" "] lastObject] stringByReplacingOccurrencesOfString:#"]" withString:#""] : #""
Sample usage:
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME(self.customer.surname)); // surname
NSLog(SOME_WAY_TO_GET_PROPERTY_NAME([[self customer] birthDate])); // birthDate
...