Cocoa structs and NSMutableArray - cocoa

I have an NSMutableArray that I'm trying to store and access some structs. How do I do this? 'addObject' gives me an error saying "Incompatible type for argument 1 of addObject". Here is an example ('in' is a NSFileHandle, 'array' is the NSMutableArray):
//Write points
for(int i=0; i<5; i++) {
struct Point p;
buff = [in readDataOfLength:1];
[buff getBytes:&(p.x) length:sizeof(p.x)];
[array addObject:p];
}
//Read points
for(int i=0; i<5; i++) {
struct Point p = [array objectAtIndex:i];
NSLog(#"%i", p.x);
}

As mentioned, NSValue can wrap a plain struct using +value:withObjCType: or -initWithBytes:objCType::
// add:
[array addObject:[NSValue value:&p withObjCType:#encode(struct Point)]];
// extract:
struct Point p;
[[array objectAtIndex:i] getValue:&p];
See the Number and Value guide for more examples.

You are getting errors because NSMutableArray can only accept references to objects, so you should wrap your structs in a class:
#interface PointClass {
struct Point p;
}
#property (nonatomic, assign) struct Point p;
This way, you can pass in instances of PointClass.
Edit: As mentioned above and below, NSValue already provides this in a more generic way, so you should go with that.

You could use NSValue, or in some cases it might make sense to use a dictionary instead of a struct.

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.

Check if NSString instance is contained in an NSArray

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
}

For Loop to do repetitive checking of variables for me

NSArray *test = [NSArray arrayWithObjects:#"22", #"3", #"22", #"5", #"1", #"0", #"2", nil];
NSArray *test2 = [NSArray arrayWithObjects:#"21", #"2", #"20", #"5", #"1", #"9", #"2", nil];
for(int i = 0; i < 7; i++) {
if ([test objectAtIndex:i] == [test2 objectAtIndex:i]); {
testVariable = testVariable + 1;
}
}
NSLog(#"%i", testVariable);
I tried the above code to test comparison of variables but it returns 7 when it should return 3. Do I need to somehow retrieve and store each array object in a local variable and compare thos against each other? Or can I do something more direct like what I tried above.
Arrays are very interesting. :)
UPDATE:
Got it to work with NSInteger.. :)
Guess I was comparing objects and not the actual integer numbers before..
You can use preprocesor
#define variable(name,number) {name##number}
and later in the loop
for (int a = 1; a <= 53; a++) {
if ((variable(taken,a) == 2) && (variable(hidden,a) == 2)) {
//Do something
}
}
The simple answer is not to use different variables. Use a collection or an array instead. Then you could have:
if (taken[a] == 2 && hidden[a] == 2) {
...
}
Think of using an array whenever you find yourself putting numeric suffixes on variables of the same type and prefix.

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;

Resources