I apologize for this basic question, but I am 2-month new to obj-c.
Problem:
I am not able to find if an object with same data already exists in the NSMutableArray.
What I am doing?
ScanDigInfoForTable* sfile = [[ScanDigInfoForTable alloc]init];
sfile.data = "myData";
int inde = [_DataList indexOfObject:sfile] ;
if(inde == -1)
[_DataList addObject:sfile];
ScanDigInfoForTable* sfile2 = [[ScanDigInfoForTable alloc]init];
sfile2.data = "myData";
inde = [_DataList indexOfObject:sfile2] ;
if(inde == -1)
[_DataList addObject:sfile2];
Issue:
The _DataList get 2 objects instead of 1. Many thanks in advance for your attention.
S.P: I already know that I may traverse the whole array in a loop in order to check the data already exists. Looking for a better solution as the array may have thousands of records.
Well, comparing two custom objects is really not that simple for the simple fact there is no defined way to declare equality. It is individual choice to define the rules for equality for the objects they are creating.
In your case, it would be two step process:
Step 1: Implement isEqual: in your ScanDigInfoForTable class. Assuming ScanDigInfoForTable is a model class and that it has three string properties - code, data & itemID (you can have any type).
- (BOOL)isEqual:(ScanDigInfoForTable *)other {
return [self.code isEqualToString:other.code] && [self.data isEqualToString:other.data] && [self.itemID isEqualToString:other.itemID];
}
Step 2: Call containsObject: method on NSMutableArray. This method would internally call isEqual: to give you the results based on the rules you defined.
// If the object does not exist in the list, we add it
if (![_DataList containsObject:sfile2]) {
[_DataList addObject:sfile2];
}
In Objective-C object equality is determined by the methods -isEqual: and -hash.
When testing object membership in a collection the items of the collection are sent isEqual:. The default implementation only compares the addresses of objects, which is why you are seeing duplicates. Your objects do no provide their own implementation of equality based on the data they contain.
To fix this you can override isEqual: to compare objects based on the data they represent. Using your example in your question, this could just be:
- (BOOL) isEqual:(id)object {
BOOL result = N0;
if (object != self){
if ([object isKindOfClass:[self class]]){
result = [[self data] isEqual:[(ScanDigInfoForTable *)object data]];
}
} else {
result = YES;
}
return result;
}
Mike Ash has a great article about implementing equality. In general, if you are implementing a custom class you should make equality a part of that.
You can user filteredArrayUsingPredicate for example
NSArray * matches = [_DataList filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"data == %# ",sfile2.data]];
if(matches.count == 0) {
[_DataList addObject:sfile2];
}
Something like this?
NSMutableSet* set1 = [NSMutableSet setWithArray:array1];
NSMutableSet* set2 = [NSMutableSet setWithArray:array2];
[set1 intersectSet:set2]; //this will give you only the obejcts that are in both sets
NSArray* result = [set1 allObjects];
This has the benefit of not looking up the objects in the array, while looping through another array, which has N^2 complexity.
and also set2 doesn't have to be mutable, might as well use just
NSSet* set2 = [NSSet setWithArray:array2];
I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];
I am trying to implement a simple method, however I am still quite a newbie on objective-c.
I have this simple method which is trying to convert from an id to a specific value in enum, if matched.
This is the enum
typedef enum {
DXTypeUnknown = 0,
DXDatasource = 1,
DXGroup = 2
} DXPropertyType;
And this is the relevant method:
-(DXPropertyType)typeFromObject:(id)_type {
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return [NSNumber numberWithInt:DXDatasource];
case DXGroup:
return [NSNumber numberWithInt:DXGroup];
default:
return [NSNumber numberWithInt:DXTypeUnknown];
}
}
The very first check I would to implement is if the id can be converted to an int, then see if it falls in the two relevant categories group or datasource, or return a default value if not. Could you tell me if the switch/case I implemented is a proper solution or not ?
I would like also this method not to causing crash of an application, so what could be advisable to check, keeping in mind that in any case the default value is to be returned.
thanks
[EDIT]
I forgot to say that this value is going to be stored in a field of a NSManagedObject, which by CoreData restriction can be an NSNumber, so probably there's a better solution instead of an enum.
It might be a good idea to include this code to check if the id can be used:
if (![_type respondsToSelector:#selector(intValue)])
return nil;
However, if you'll always pass a NSNumber go ahead and declare the method as:
- (DXPropertyType)typeFromObject:(NSNumber)_type;
In your code, you're returning a NSNumber. I don't think that's what you really
want, as you'd be doing nothing with the NSNumber passed. Return the enum
item:
-(DXPropertyType)typeFromObject:(id)_type {
if (![_type respondsToSelector:#selector(intValue)])
return nil;
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return DXDatasource;
case DXGroup:
return DXGroup;
default:
return DXTypeUnknown;
}
}
And then this can be simplified to:
- (DXPropertyType)typeFromObject:(id)_type {
if ([_type respondsToSelector:#selector(intValue)]) {
int t = [_type intValue];
DXPropertyType property_t;
if (t >= 1 && t <= 2)
property_t = t;
else
property_t = DXTypeUnknown;
return property_t;
}
return nil;
}
Your switch statement is a good solution and will not cause a crash.
However, your method returns a NSNumber when it expects a different return. I suggest changing the method to
-(NSNumber)typeFromObject:(id)_type
You specify that your method returns an enum, but you return objects. So either return the enum values or specify the return type to be NSNumber *.
A different solution could be using singleton objects instead of an enum, but that's probably more work than it's worth. Think [NSNull null].
Hey guys, check this out. I have a function that treats for me a string. No matter what it does, i just want to knwo if is possible to this function return the result for the place that it was executed. I mean, check this:
[self priceFormat:#"1"];
priceLabel.text = price;
-(void) priceFormat:(NSString*)price {
price = #"2";
}
I just want to my function treats the string and return it to the same place that it was executed.
Thanks!
Three ways to do this
Way one, using a pointer
- (void)priceFormat:(NSString **)price {
*price = #"2";
}
Wat two, using an instance variable
What you might want instead is an ivar. In the interface (most often the h file) of your class:
NSString *price;
and in the implementation (the m or mm file):
- (void)priceFormat:(NSString *)price {
price = #"2";
}
I have created an example of this here.
If you want the price to be available to other objects as well (not just self), you might want to create a property for it and synthesize it. Then use self.price = #"2"; instead. More on this here: http://MacDeveloperTips.com/objective-c/objective-c-properties-setters-and-dot-syntax.html
Just make sure you make it a copy property (NSString in use)!
Way three using return
Note, that you can also return directly from a method:
- (NSString *)priceFormat:(NSString *)price {
return #"2";
}
priceLabel.text = [self priceFormat:#"1"];
I'm doing some Objective-C programming that involves parsing an NSXmlDocument and populating an objects properties from the result.
First version looked like this:
if([elementName compare:#"companyName"] == 0)
[character setCorporationName:currentElementText];
else if([elementName compare:#"corporationID"] == 0)
[character setCorporationID:currentElementText];
else if([elementName compare:#"name"] == 0)
...
But I don't like the if-else-if-else pattern this produces. Looking at the switch statement I see that i can only handle ints, chars etc and not objects... so is there a better implementation pattern I'm not aware of?
BTW I did actually come up with a better solution for setting the object's properties, but I want to know specifically about the if-else vs switch pattern in Objective-C
You should take advantage of Key-Value Coding:
[character setValue:currentElementText forKey:elementName];
If the data is untrusted, you might want to check that the key is valid:
if (![validKeysCollection containsObject:elementName])
// Exception or error
I hope you'll all forgive me for going out on a limb here, but I would like to address the more general question of parsing XML documents in Cocoa without the need of if-else statements. The question as originally stated assigns the current element text to an instance variable of the character object. As jmah pointed out, this can be solved using key-value coding. However, in a more complex XML document this might not be possible. Consider for example the following.
<xmlroot>
<corporationID>
<stockSymbol>EXAM</stockSymbol>
<uuid>31337</uuid>
</corporationID>
<companyName>Example Inc.</companyName>
</xmlroot>
There are multiple approaches to dealing with this. Off of the top of my head, I can think of two using NSXMLDocument. The first uses NSXMLElement. It is fairly straightforward and does not involve the if-else issue at all. You simply get the root element and go through its named elements one by one.
NSXMLElement* root = [xmlDocument rootElement];
// Assuming that we only have one of each element.
[character setCorperationName:[[[root elementsForName:#"companyName"] objectAtIndex:0] stringValue]];
NSXMLElement* corperationId = [root elementsForName:#"corporationID"];
[character setCorperationStockSymbol:[[[corperationId elementsForName:#"stockSymbol"] objectAtIndex:0] stringValue]];
[character setCorperationUUID:[[[corperationId elementsForName:#"uuid"] objectAtIndex:0] stringValue]];
The next one uses the more general NSXMLNode, walks through the tree, and directly uses the if-else structure.
// The first line is the same as the last example, because NSXMLElement inherits from NSXMLNode
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
if([[aNode name] isEqualToString:#"companyName"]){
[character setCorperationName:[aNode stringValue]];
}else if([[aNode name] isEqualToString:#"corporationID"]){
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
if([[aNode name] isEqualToString:#"stockSymbol"]){
[character setCorperationStockSymbol:[aNode stringValue]];
}else if([[aNode name] isEqualToString:#"uuid"]){
[character setCorperationUUID:[aNode stringValue]];
}
}
}
}
This is a good candidate for eliminating the if-else structure, but like the original problem, we can't simply use switch-case here. However, we can still eliminate if-else by using performSelector. The first step is to define the a method for each element.
- (NSNode*)parse_companyName:(NSNode*)aNode
{
[character setCorperationName:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID:(NSNode*)aNode
{
NSXMLNode* correctParent = aNode;
while((aNode = [aNode nextNode]) == nil && [aNode parent != correctParent){
[self invokeMethodForNode:aNode prefix:#"parse_corporationID_"];
}
return [aNode previousNode];
}
- (NSNode*)parse_corporationID_stockSymbol:(NSNode*)aNode
{
[character setCorperationStockSymbol:[aNode stringValue]];
return aNode;
}
- (NSNode*)parse_corporationID_uuid:(NSNode*)aNode
{
[character setCorperationUUID:[aNode stringValue]];
return aNode;
}
The magic happens in the invokeMethodForNode:prefix: method. We generate the selector based on the name of the element, and perform that selector with aNode as the only parameter. Presto bango, we've eliminated the need for an if-else statement. Here's the code for that method.
- (NSNode*)invokeMethodForNode:(NSNode*)aNode prefix:(NSString*)aPrefix
{
NSNode* ret = nil;
NSString* methodName = [NSString stringWithFormat:#"%#%#:", prefix, [aNode name]];
SEL selector = NSSelectorFromString(methodName);
if([self respondsToSelector:selector])
ret = [self performSelector:selector withObject:aNode];
return ret;
}
Now, instead of our larger if-else statement (the one that differentiated between companyName and corporationID), we can simply write one line of code
NSXMLNode* aNode = [xmlDocument rootElement];
while(aNode = [aNode nextNode]){
aNode = [self invokeMethodForNode:aNode prefix:#"parse_"];
}
Now I apologize if I got any of this wrong, it's been a while since I've written anything with NSXMLDocument, it's late at night and I didn't actually test this code. So if you see anything wrong, please leave a comment or edit this answer.
However, I believe I have just shown how properly-named selectors can be used in Cocoa to completely eliminate if-else statements in cases like this. There are a few gotchas and corner cases. The performSelector: family of methods only takes 0, 1, or 2 argument methods whose arguments and return types are objects, so if the types of the arguments and return type are not objects, or if there are more than two arguments, then you would have to use an NSInvocation to invoke it. You have to make sure that the method names you generate aren't going to call other methods, especially if the target of the call is another object, and this particular method naming scheme won't work on elements with non-alphanumeric characters. You could get around that by escaping the XML element names in your method names somehow, or by building an NSDictionary using the method names as the keys and the selectors as the values. This can get pretty memory intensive and end up taking a longer time. performSelector dispatch like I described is pretty fast. For very large if-else statements, this method may even be faster than an if-else statement.
If you want to use as little code as possible, and your element names and setters are all named so that if elementName is #"foo" then setter is setFoo:, you could do something like:
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", [elementName capitalizedString]]);
[character performSelector:selector withObject:currentElementText];
or possibly even:
[character setValue:currentElementText forKey:elementName]; // KVC-style
Though these will of course be a bit slower than using a bunch of if statements.
[Edit: The second option was already mentioned by someone; oops!]
Dare I suggest using a macro?
#define TEST( _name, _method ) \
if ([elementName isEqualToString:# _name] ) \
[character _method:currentElementText]; else
#define ENDTEST { /* empty */ }
TEST( "companyName", setCorporationName )
TEST( "setCorporationID", setCorporationID )
TEST( "name", setName )
:
:
ENDTEST
One way I've done this with NSStrings is by using an NSDictionary and enums. It may not be the most elegant, but I think it makes the code a little more readable. The following pseudocode is extracted from one of my projects:
typedef enum { UNKNOWNRESIDUE, DEOXYADENINE, DEOXYCYTOSINE, DEOXYGUANINE, DEOXYTHYMINE } SLSResidueType;
static NSDictionary *pdbResidueLookupTable;
...
if (pdbResidueLookupTable == nil)
{
pdbResidueLookupTable = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInteger:DEOXYADENINE], #"DA",
[NSNumber numberWithInteger:DEOXYCYTOSINE], #"DC",
[NSNumber numberWithInteger:DEOXYGUANINE], #"DG",
[NSNumber numberWithInteger:DEOXYTHYMINE], #"DT",
nil];
}
SLSResidueType residueIdentifier = [[pdbResidueLookupTable objectForKey:residueType] intValue];
switch (residueIdentifier)
{
case DEOXYADENINE: do something; break;
case DEOXYCYTOSINE: do something; break;
case DEOXYGUANINE: do something; break;
case DEOXYTHYMINE: do something; break;
}
The if-else implementation you have is the right way to do this, since switch won't work with objects. Apart from maybe being a bit harder to read (which is subjective), there is no real downside in using if-else statements this way.
Although there's not necessarily a better way to do something like that for one time use, why use "compare" when you can use "isEqualToString"? That would seem to be more performant since the comparison would halt at the first non-matching character, rather than going through the whole thing to calculate a valid comparison result (though come to think of it the comparison might be clear at the same point) - also though it would look a little cleaner because that call returns a BOOL.
if([elementName isEqualToString:#"companyName"] )
[character setCorporationName:currentElementText];
else if([elementName isEqualToString:#"corporationID"] )
[character setCorporationID:currentElementText];
else if([elementName isEqualToString:#"name"] )
There is actually a fairly simple way to deal with cascading if-else statements in a language like Objective-C. Yes, you can use subclassing and overriding, creating a group of subclasses that implement the same method differently, invoking the correct implementation at runtime using a common message. This works well if you wish to choose one of a few implementations, but it can result in a needless proliferation of subclasses if you have many small, slightly different implementations like you tend to have in long if-else or switch statements.
Instead, factor out the body of each if/else-if clause into its own method, all in the same class. Name the messages that invoke them in a similar fashion. Now create an NSArray containing the selectors of those messages (obtained using #selector()). Coerce the string you were testing in the conditionals into a selector using NSSelectorFromString() (you may need to concatenate additional words or colons to it first depending on how you named those messages, and whether or not they take arguments). Now have self perform the selector using performSelector:.
This approach has the downside that it can clutter-up the class with many new messages, but it's probably better to clutter-up a single class than the entire class hierarchy with new subclasses.
Posting this as a response to Wevah's answer above -- I would've edited, but I don't have high enough reputation yet:
unfortunately the first method breaks for fields with more than one word in them -- like xPosition. capitalizedString will convert that to Xposition, which when combined with the format give you setXposition: . Definitely not what was wanted here. Here is what I'm using in my code:
NSString *capName = [elementName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[elementName substringToIndex:1] uppercaseString]];
SEL selector = NSSelectorFromString([NSString stringWithFormat:#"set%#:", capName]);
Not as pretty as the first method, but it works.
I have come up with a solution that uses blocks to create a switch-like structure for objects. There it goes:
BOOL switch_object(id aObject, ...)
{
va_list args;
va_start(args, aObject);
id value = nil;
BOOL matchFound = NO;
while ( (value = va_arg(args,id)) )
{
void (^block)(void) = va_arg(args,id);
if ( [aObject isEqual:value] )
{
block();
matchFound = YES;
break;
}
}
va_end(args);
return matchFound;
}
As you can see, this is an oldschool C function with variable argument list. I pass the object to be tested in the first argument, followed by the case_value-case_block pairs. (Recall that Objective-C blocks are just objects.) The while loop keeps extracting these pairs until the object value is matched or there are no cases left (see notes below).
Usage:
NSString* str = #"stuff";
switch_object(str,
#"blah", ^{
NSLog(#"blah");
},
#"foobar", ^{
NSLog(#"foobar");
},
#"stuff", ^{
NSLog(#"stuff");
},
#"poing", ^{
NSLog(#"poing");
},
nil); // <-- sentinel
// will print "stuff"
Notes:
this is a first approximation without any error checking
the fact that the case handlers are blocks, requires additional care when it comes to visibility, scope and memory management of variables referenced from within
if you forget the sentinel, you are doomed :P
you can use the boolean return value to trigger a "default" case when none of the cases have been matched
The most common refactoring suggested for eliminating if-else or switch statements is introducing polymorphism (see http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html). Eliminating such conditionals is most important when they are duplicated. In the case of XML parsing like your sample you are essentially moving the data to a more natural structure so that you won't have to duplicate the conditional elsewhere. In this case the if-else or switch statement is probably good enough.
In this case, I'm not sure if you can easily refactor the class to introduce polymorphism as Bradley suggests, since it's a Cocoa-native class. Instead, the Objective-C way to do it is to use a class category to add an elementNameCode method to NSSting:
typedef enum {
companyName = 0,
companyID,
...,
Unknown
} ElementCode;
#interface NSString (ElementNameCodeAdditions)
- (ElementCode)elementNameCode;
#end
#implementation NSString (ElementNameCodeAdditions)
- (ElementCode)elementNameCode {
if([self compare:#"companyName"]==0) {
return companyName;
} else if([self compare:#"companyID"]==0) {
return companyID;
} ... {
}
return Unknown;
}
#end
In your code, you could now use a switch on [elementName elementNameCode] (and gain the associated compiler warnings if you forget to test for one of the enum members etc.).
As Bradley points out, this may not be worth it if the logic is only used in one place.
What we've done in our projects where we need to so this sort of thing over and over, is to set up a static CFDictionary mapping the strings/objects to check against to a simple integer value. It leads to code that looks like this:
static CFDictionaryRef map = NULL;
int count = 3;
const void *keys[count] = { #"key1", #"key2", #"key3" };
const void *values[count] = { (uintptr_t)1, (uintptr_t)2, (uintptr_t)3 };
if (map == NULL)
map = CFDictionaryCreate(NULL,keys,values,count,&kCFTypeDictionaryKeyCallBacks,NULL);
switch((uintptr_t)CFDictionaryGetValue(map,[node name]))
{
case 1:
// do something
break;
case 2:
// do something else
break;
case 3:
// this other thing too
break;
}
If you're targeting Leopard only, you could use an NSMapTable instead of a CFDictionary.
Similar to Lvsti I am using blocks to perform a switching pattern on objects.
I wrote a very simple filter block based chain, that takes n filter blocks and performs each filter on the object.
Each filter can alter the object, but must return it. No matter what.
NSObject+Functional.h
#import <Foundation/Foundation.h>
typedef id(^FilterBlock)(id element, NSUInteger idx, BOOL *stop);
#interface NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks;
#end
NSObject+Functional.m
#implementation NSObject (Functional)
-(id)processByPerformingFilterBlocks:(NSArray *)filterBlocks
{
__block id blockSelf = self;
[filterBlocks enumerateObjectsUsingBlock:^( id (^block)(id,NSUInteger idx, BOOL*) , NSUInteger idx, BOOL *stop) {
blockSelf = block(blockSelf, idx, stop);
}];
return blockSelf;
}
#end
Now we can set up n FilterBlocks to test for the different cases.
FilterBlock caseYES = ^id(id element, NSUInteger idx, BOOL *breakAfter){
if ([element isEqualToString:#"YES"]) {
NSLog(#"You did it");
*breakAfter = YES;
}
return element;
};
FilterBlock caseNO = ^id(id element, NSUInteger idx, BOOL *breakAfter){
if ([element isEqualToString:#"NO"] ) {
NSLog(#"Nope");
*breakAfter = YES;
}
return element;
};
Now we stick those block we want to test as a filter chain in an array:
NSArray *filters = #[caseYES, caseNO];
and can perform it on an object
id obj1 = #"YES";
id obj2 = #"NO";
[obj1 processByPerformingFilterBlocks:filters];
[obj2 processByPerformingFilterBlocks:filters];
This approach can be used for switching but also for any (conditional) filter chain application, as the blocks can edit the element and pass it on.