Check if NSString instance is contained in an NSArray - cocoa

I have an array with a bunch of strings and I want to check if a certain string is contained in the array. If I use the containsObject: message on the array, I'm getting correct results. Do all NSString objects with the same string point to the same object? Or why is the containsObject: working?
NSArray *stringArray = [NSArray arrayWithObjects:#"1",#"2",#"3",anotherStringValue, nil];
if([stringArray containsObject:#"2"]){
//DO SOMETHING
}

Yes, hard-coded NSStrings (string literals) (that is any #"..." in your source code) are turned into strings that exist indefinitely while your process is running.
However NSArray's containsObject: methods calls isEqual: on its objects, hence even a dynamically created string such as [NSString stringWithFormat:#"%d", 2] would return YES in your sample snippet.
This is because NSString's isEqual: (or more precisely its isEqualToString:) method is implemented to be content aware (vs. comparing pointer identities) and thus returns YES for any pair of strings containing the very same sequence of characters (at time of comparison), no matter how and when they were created.
To check for equal (pointer-)identity you'd have to enumerate your array and compare via
NSString *yourString = #"foo";
BOOL identicalStringFound = NO;
for (NSString *someString in stringArray) {
if (someString == yourString) {
identicalStringFound = YES;
break;
}
}
(which you most likely wouldn't want, though).
Or in a more convenient fashion:
BOOL identicalStringFound = [stringArray indexOfObjectIdenticalTo:someString] != NSNotFound;
(you most likely wouldn't want this one either).
Summing up:
So the reason you're getting a positive reply from containsObject: is NOT because literal strings share the same constant instance, BUT because containsObject: by convention calls isEqual:, which is content aware.
You might want to read the (short) documentation for isEqual: from the NSObject protocol.

containsObject: performs a value check, not a pointer check. It uses the isEqual: method defined by NSObject and overridden by other objects for testing. Therefore, if two strings contain the same sequence of characters, they will be considered the same.
The distinction between pointer testing and value testing is very important in some cases. Constant strings defined in source code are combined by the compiler so that they are the same object. However, strings created dynamically are not the same object. Here is an example program which will demonstrate this:
int main(int argc, char **argv) {
NSAutoreleasePool *p = [NSAutoreleasePool new];
NSString *constantString = #"1";
NSString *constantString2 = #"1";
NSString *dynamicString = [NSString stringWithFormat:#"%i",1];
NSArray *theArray = [NSArray arrayWithObject:constantString];
if(constantString == constantString2) NSLog(#"constantString == constantString2");
else NSLog(#"constantString != constantString2");
if(constantString == dynamicString) NSLog(#"constantString == dynamicString");
else NSLog(#"constantString != dynamicString");
if([constantString isEqual:dynamicString]) NSLog(#"[constantString isEqual:dynamicString] == YES");
else NSLog(#"[constantString isEqual:dynamicString] == NO");
NSLog(#"theArray contains:\n\tconstantString: %i\n\tconstantString2: %i\n\tdynamicString: %i",
[theArray containsObject:constantString],
[theArray containsObject:constantString2],
[theArray containsObject:dynamicString]);
}
The output of this program is:
2011-04-27 17:10:54.686 a.out[41699:903] constantString == constantString2
2011-04-27 17:10:54.705 a.out[41699:903] constantString != dynamicString
2011-04-27 17:10:54.706 a.out[41699:903] [constantString isEqual:dynamicString] == YES
2011-04-27 17:10:54.706 a.out[41699:903] theArray contains:
constantString: 1
constantString2: 1
dynamicString: 1

You can use containsObject to findout if certain string is exist,
NSArray *stringArray = [NSArray arrayWithObjects:#"1",#"2",#"3",anotherStringValue, nil];
if ( [stringArray containsObject: stringToFind] ) {
// if found
} else {
// if not found
}

Related

NSString pointer passed to function... not keeping the value I set

I'm not sure what I'm doing wrong here. I've also tried setting s1..3 in foo by using:
s1 = [[NSString alloc] initWithString:[filepaths objectAtIndex:0]];
Context below:
void foo(NSString *s1, NSString *s2, NSString *s3){
//assign long string to NSString *fps
//...
//break fps into smaller bits
NSArray *filepaths = [fps componentsSeparatedByString:#"\n"];
//the above worked! now let's assign them to the pointers
s1 = [filepaths objectAtIndex:0];
//repeat for s2 and s3
NSLog(#"%#",s1); //it worked! we're done in this function
}
int main(int argc, const char * argv[]){
NSString *s1 = nil; //s2 and s3 as well
foo(s1,s2,s3); //this should work
NSLog(#"%#",s1); //UH OH, this is null!
return 0;
}
No.
You are passing in pointers to objects which can be mutated locally. You are not changing the original objects, as you might think from plain C.
If you want to use this method (which I would not recommend - it's really odd to see in Cocoa except in the case of NSError), you would have something like:
void foo(NSString **s1, NSString **s2, NSString **s3) {
*s1 = [filepaths objectAtIndex:0]; // etc.
}
You would then pass in &s1 as the argument.
This will, of course, clobber whatever was in s1, potentially cause memory leaks, thread unsafety, etc., unless you are really careful. Which is why I say you usually won't do this.
Functions take a local copy of their arguments, so you're modifying a copy of the NSString* not the original. This is called "pass by value". What you want is "pass by reference," which looks like this:
void foo(NSString** s1) {
if(s1) *s1 = #"Different string";
}
int main(int argc, const char* argv[]){
NSString* s1 = nil;
foo(&s1);
NSLog(#"%#", s1);
return EXIT_SUCCESS;
}
It boils down to how well you understand pointers in C. It would be a good idea to read up on the "address of" operator &, and the dereference operator *, and just C pointers in general.

Runtime error when NSString Object is released

In my Function below if I remove the tempString release statement it works just fine but with it, there is ALWAYS a runtime error. It is a simple function that displays an array in an NSTextField either _stackDisp1 or _stackDisp2 but for some reason releasing the string creates a runtime error Any help?
- (void) displayArr:(NSMutableArray*)stack{
NSTextField *myObj;
if([stack count] <= 10) myObj = _stackDisp1;
else myObj = _stackDisp2;
NSString *tempString = [[NSString alloc]initWithString:#""];
for(NSString *i in stack){
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
}
[myObj setStringValue:tempString];
[tempString release];
}
That's because
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
creates a new autoreleased object assigning it to your variable tempString. The pointer to the first object gets lost and you end up over-releasing an autoreleased object. Just change the initial assignment to
NSString *tempString = #"";
and remove the [tempString release] line.
In the for loop you're assigning tempString to an autoreleased string:
tempString = [NSString stringWithFormat:#"%#\n%#",tempString,i];
releasing it manually results in a BAD_ACCESS.
Also you are probably looking for this:
- (void) displayArr:(NSMutableArray*)stack{
NSTextField *myObj = ([stack count] <= 10) ? _stackDisp1 : _stackDisp2;
[myObj setStringValue:[stack componentsJoinedByString:#"\n"]];
}
The declaration/assignment of myObj was a bit too verbose for my taste,
so I used a ternary operator instead (it's use is not essiential though. Just a matter of style.).

Problem with NSNumber and plotting a graph with Core-Plot

I try to plot a Bar Chart with Core-Plot with an Array (content are NSIntegers) given one view before.
After transfering the Array in an NSInteger, i must convert it into a NSDecimalNumber, and in this process, my NSInteger (for example 45) becomes "60900224"...
Here's the code extract:
-(NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{ NSInteger *values = [Werte objectAtIndex:index];
NSDecimalNumber *num = nil;
if ( [plot isKindOfClass:[CPBarPlot class]] ) {
switch ( fieldEnum ) {
case CPBarPlotFieldBarLocation:
num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:index];
break;
case CPBarPlotFieldBarLength:
//num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:(index+1)*(index+1)];
num = [NSNumber numberWithInt:values];
if ( [plot.identifier isEqual:#"Bar Plot 2"] )
num = [num decimalNumberBySubtracting:[NSDecimalNumber decimalNumberWithString:#"10"]];
break;
}
}
return num;
}
Thanks for help!!
NSInteger is not an object type and can't be stored in an NSArray (which your Werte appears to be). You seem to be implicitly converting from a pointer to an integer.
Instead, you should always put NSNumber objects into the array, and then get NSInteger values out of those via integerValue:
NSInteger value = [[Werte objectAtIndex:index] integerValue];

NSArray filled with bool objects

I have an NSArray filled with bools (expressed as a number), and I need to test to see if any object within the array is equal to 1. How can I do it?
BOOLs are not objects. Assuming you mean some object representing a boolean like NSNumber that implements a proper isEqual:, you could just do something like [array containsObject:[NSNumber numberWithBool:YES]].
As Chuck says, use -[NSArray containsObject:[NSNumber numberWithBool:YES]]. As a thought experiment, here are some other ways to accomplish the goal...
You can do this using an NSPredicate or using the new blocks API:
NSArray *myArr //decleared, initialized and filled
BOOL anyTrue = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"boolValue == 1"]].count > 0;
or
BOOL anyTrue = [myArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
if([obj boolValue]) {
*stop = YES;
}
return [obj boolValue];
}].count > 0;
You can also use Key-Value coding, though I'm not sure of its relative efficiency:
[[myArray valueForKeyPath:#"#sum.boolValue"] integerValue] > 0;

cocoa: NSString not removing all the characters

I have an int and for some reason it isn't working after 16 or so. Here's my code:
NSArray *sortedArray;
sortedArray = [doesntContainAnother sortedArrayUsingFunction:firstNumSort context:NULL];
int count2 = [sortedArray count];
//NSLog(#"%d", count2);
int z = 0;
while (z < count2) {
NSString *myString = [sortedArray objectAtIndex:z];
NSString *intstring = [NSString stringWithFormat:#"%d", z];
NSString *stringWithoutSpaces;
stringWithoutSpaces = [[myString stringByReplacingOccurrencesOfString:intstring
withString:#""] mutableCopy];
[hopefulfinal addObject:stringWithoutSpaces];
NSLog(#"%#", [hopefulfinal objectAtIndex:z]);
z++;
}
Edit: It's not the int, it's the stringWithoutSpaces line... I can't figure out what's causing it.
So it (the NSLog, see above the z++) looks like this:
"Here"
"whatever"
"17 whatevere"
"18 this"
etc.
I'm guessing this is related to your earlier question Sort NSArray’s by an int contained in the array, and that you're trying to strip the leading number and whitespace from an array that looks like the one you had in that question:
"0 Here is an object"
"1 What the heck, here's another!"
"2 Let's put 2 here too!"
"3 Let's put this one right here"
"4 Here's another object"
Without know the full input, I'd guess that your code is likely failing because the leading numbers and the value of z are getting out of sync. Since you don't seem to actually care what the leading number is and just want to vamoose it, I'd recommend a different approach that scans for leading digits and extracts the substring from the position where those digits end:
NSArray *array = [NSArray arrayWithObjects:#"1 One",
#"2 Two",
#"5 Five",
#"17 Seventeen",
nil];
NSMutableArray *results = [NSMutableArray array];
NSScanner *scanner;
NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet];
for (NSString *item in array) {
scanner = [NSScanner scannerWithString:item];
[scanner scanInteger:NULL]; // throwing away the BOOL return value...
// if string does not start with a number,
// the scanLocation will be 0, which is good.
[results addObject:[[item substringFromIndex:[scanner scanLocation]]
stringByTrimmingCharactersInSet:whitespace]];
}
NSLog(#"Resulting array is: %#", results);
// Resulting array is: (
// One,
// Two,
// Five,
// Seventeen
// )
)

Resources