Issue adding strings to a NSMutableArray using addObject - objective-c

I've searched other questions and can't seem to find a similar problem. Either I am something completely wrong or I am blind. But here goes the code:
#autoreleasepool {
NSMutableString *sense = [[NSMutableString alloc] init];
NSMutableArray *senses = [[NSMutableArray alloc] init];
....... other code which initializes rL and count/length .......
for (index=0;index<count;index++) {
for (j=0;j<length;j++) {
c = [rL characterAtIndex:j];
switch (c) {
case '.':
[senses addObject:sense];
[sense setString:#""];
break;
default:
[sense appendFormat:#"%c",c];
break;
}
}
}
}
When I do this, and iterate, in debug mode, I see that all objects in senses are same as whatever the last value of sense was.
what am I doing wrong?

"sense" is always the same object. It is a mutable string, so the contents can change, but it is always the same object. So senses will contain that single object, multiple times. You could instead use
[senses addObject:[sense copy]];

The immediate solution could be to change:
[senses addObject:sense];
to:
[senses addObject:[NSString stringWithString:sense]];
This will add unique instances instead of adding the same mutable string over and over.
But it appears you are splitting a string up using the "." characters as a delimiter.
There's an easier way:
NSArray *senses = [rl componentsSeparatedByString:#"."];
That's it - one line.

Related

Objective C code debug issue

I am trying to compile and run a simple objective c code BUT I am doing it on Windows.I am using the GNU Step and it is extremely hard for me to debug it and understand what is going on runtime.I am a .NET developer and I always use the Debugger in Visual Studio to follow the data flow and stuf but here ...... it is realy annoying.I don't have a Mac Book so I don't have the XCode too.
Can anybody tell me what is the problem in that peace of code?It is quite simple and it would be great if someone who has a Mac could debug it for me and tell me what is wrong.
The idea of the code is that it reads out a text file line by line and then on every 3 lines of code it makes an Object of NSMutableArray and adds it to another NSMutableArray.Here it is:
The read_line function:
int read_line(FILE *in, char *buffer, size_t max)
{
return fgets(buffer, max, in) == buffer;
}
The content of the text file:
Sophie Ellis-Bextor
71222
5.01
Inna Morales
61223
6.00
Kortez Domingues
41231
3.25
The code in the main:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
FILE *in;
if((in = fopen("C:...\\Input.txt", "rt")) != NULL)
{
char line[256];
NSMutableArray* resultArray = [[NSMutableArray alloc] init];
while(read_line(in, line, sizeof line))
{
NSString *currentLine = [[NSString alloc] initWithUTF8String:line];
[resultArray addObject:currentLine];
}
NSMutableArray*resultObjectsArray =[[NSMutableArray alloc] init];
NSMutableArray*tmpArray =[[NSMutableArray alloc] init];
for(int i=0 ; i <[resultArray count];i++)
{
if(i%4 == 3)
{
[resultObjectsArray addObject:tmpArray];
[tmpArray removeAllObjects];
NSLog(#"Here we add a new object");
}
else
{
[tmpArray addObject:[resultArray objectAtIndex:i]];
NSLog(#"%#",[resultArray objectAtIndex:i]);
}
}
fclose(in);
NSLog(#"First object in the result Array: %#",[[resultObjectsArray objectAtIndex:0] objectAtIndex:0]);
}
[pool drain];
All that I can see is that on the
NSLog(#"First object in the result Array: %#",[[resultObjectsArray objectAtIndex:0] objectAtIndex:0]);
line I get the next error:
Uncaught Exception NSRangeException, reason:Index 0 is out of range 0 (in 'objectAtIndex:')
I'm assuming you accidentally left off a (blank) line at the end of your input file, since the exception you mention doesn't happen with the file literally as given. I've edited your question to reflect this. Then, assuming the file is fixed in that way:
The immediate cause of the exception is that tmpArray is empty when the final NSLog is called. The reason for that is that you reuse the same tmpArray object each time through the preceding loop; you add tmpArray to resultObjectsArray, and then clear out tmpArray and start adding additional members to it. The reason this is a problem is that array elements are added by reference, not copied; you need to either copy tmpArray each time or make a brand new temporary object.
So, when you reach the final NSLog, the first element in resultObjectsArray is the same object as tmpArray; you've just called [tmpArray removeAllObjects] in the if(i%4 == 3) conditional, so it's empty; thus the second objectAtIndex:0 raises an exception.
Now, as to why the original version of the input file (the one without the blank line at the end) does not trigger the same exception (but also doesn't function correctly): your for loop goes through line by line, and adds each line to tmpArray, until it reaches a line whose index is evenly divisible by 4, at which point it clears tmpArray so it can start adding more to it the next time. The original version of the input file you gave had 11 lines, so tmpArray wasn't cleared at the end; thus tmpArray, as well as the identical objects serving as elements of resultObjectsArray, contained the last three lines read. Since it wasn't empty, objectAtIndex:0 didn't raise an except. But at the same time, the logic was wrong, since the NSLog is supposed to be returning the first element in the array, but as it happens all the elements are references to that same tmpArray object, which contains only the last stanza of lines.

How to efficiently access large objects in Obj-C using objectForKey and objectAtIndex?

If I have a large NSDirectory typically from a parsed JSON-object I can access this object with code like so:
[[[[[obj objectForKey:#"root"] objectForKey:#"key1"] objectAtIndex:idx] objectForKey:#"key2"] objectAtIndex:idz];
The line might be a lot longer than this.
Can I optimize this code in any way? At least make it easier to read?
This line will also generate a runtime-error if the object does not correspond, what is the most efficient way to avoid that?
If you were using -objectForKey: for everything you could use -valueForKeyPath:, as in
[obj valueForKeyPath:#"key1.key2.key3.key4"]
However, this doesn't work when you need to use -objectAtIndex:. I don't think there's any good solution for you. -valueForKeyPath: also wouldn't solve the problem of the runtime errors.
If you truly want a simple way to do this you could write your own version of -valueForKeyPath: (call it something else) that provides a syntax for specifying an -objectAtIndex: instead of a key, and that does the appropriate dynamic checks to ensure the object actually responds to the method in question.
If you want easier to read code you can split the line into several lines like this
MyClass *rootObject = [obj objectForKey:#"root"];
MyClass *key1Object = [rootObject objectForKey:#"key1"];
MyClass *myObject = [key1Object objectAtIndex:idx];
...
and so forth.
I think, you can create some array, that will contain full "path" to your object. The only thing, you need to store your indexes somehow, maybe in NSNumber, in this case you cannot use NSNumber objects as keys in your dictionaries. Then create a method, that will return needed object for this given "path". smth like
NSMutableArray* basePath = [NSMutableArray arrayWithObjects: #"first", [NSNumber numberWithInt:index], nil];
id object = [self objectForPath:basePath inContainer:container];
- (id) objectForPath:(NSMutableArray*)basePath inContainer:(id)container
{
id result = nil;
id pathComponent = [basePath objectAtIndex: 0];
[basePath removeObjectAtIndex: 0];
// check if it is a number with int index
if( [pathComponent isKindOfClass:[NSNumber class]] )
{
result = [container objectAtIndex: [pathComponent intValue]];
}
else
{
result = [container objectForKey: pathComponent];
}
assert( result != nil );
// check if it is need to continue searching object
if( [basePath count] > 0 )
{
return [self objectForPath:basePath inContainer: result];
}
else
{
return result;
}
}
this is just an idea, but I hope you understand what I mean. And as Kevin mentioned above, if you don't have indexes, you can use key-value coding.
Don't know if it can suit you, but you could also give a try to blocks, I always find them very convenient. At least they made code much more readable.
NSArray *filter = [NSArray arrayWithObjects:#"pathToFind", #"pathToFind2",nil];
NSPredicate *filterBlock = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
NSArray *root = (NSArray*)obj;
// cycle the array and found what you need.
// eventually implementing some sort of exit strategy
}];
[rootObject filteredArrayUsingPredicate:filterBlock];

How to change this so that it returns arrays

The following code works perfectly and shows the correct output:
- (void)viewDidLoad {
[super viewDidLoad];
[self expand_combinations:#"abcd" arg2:#"" arg3:3];
}
-(void) expand_combinations: (NSString *) remaining_string arg2:(NSString *)s arg3:(int) remain_depth
{
if(remain_depth==0)
{
printf("%s\n",[s UTF8String]);
return;
}
NSString *str = [[NSString alloc] initWithString:s];
for(int k=0; k < [remaining_string length]; ++k)
{
str = [s stringByAppendingString:[[remaining_string substringFromIndex:k] substringToIndex:1]];
[self expand_combinations:[remaining_string substringFromIndex:k+1] arg2:str arg3:remain_depth - 1];
}
return;
}
However, instead of outputting the results, I want to return them to an NSArray. How can this code be changed to do that? I need to use the information that this function generates in other parts of my program.
There are several things that you need to change in your code.
First - consider changing the name of your method to something more legible and meaningful than -expand_combinations:arg2:arg3.
Second - you have a memory leak. You don't need to set allocate memory and initialize str with the string s, because you change its value right away in the loop without releasing the old value.
Third - take a look at NSMutableArray. At the beginning of the method, create an array with [NSMutableArray array], and at every line that you have printf, instead, add the string to the array. Then return it.
basicaly you have:
create mutable array in viewDidLoad before [self expand_combinations ...
add aditional parameter (mutable array) to expand_combinations
populate array in expand_combinations

Objective-c: Dynamic Class Names

I'm not sure if I worded the subject correctly. I am looping through an array, within each loop I am trying to instantiate a class, but I want to dynamically create the name. Like so:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
NSString* thisad = [NSString stringWithFormat:#"ad%d", i];
NSLog(#"%#", thisad);
AdData* thisad = [AdData new];
}
In the example above I want AdData* thisad... to be named dynamically - "ad1", "ad2", "ad3"...and so on. I get a conflicting type error.
This code also generated an error:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* [NSString stringWithFormat:#"ad%d", i] = [AdData new];
}
Is there a way to do this?
You can't do that in Objective-C.
Use a NSString to AdData map--it'll do basically the same thing!
**edit: To clarify, use an:
NSMutableDictionary *dict;
with keys that are NSString* objects containing the ad names, and values that are the AdData* objects.
i.e.
[dict setValue:ad1 forKey:#"ad1"];
to set the values, and
[dict valueForKey:#"ad1"];
to get the values. (ignore the obvious memory leaks there with the strings...)
This isn't possible. While Objective-C is very dynamic, it's not that dynamic.
The suggested way to do this would be to create your instances and put them into an array, not assigning them to explicitly named variables.
You can then refer to them individually using their index in the array.
Something like this:
NSMutableArray *ads = [NSMutableArray array];
for(NSString* thisdatarow in filedata) {
AdData* thisad = [[[AdData alloc] init] autorelease];
[ads addObject:thisad];
}
// get third ad:
AdData *ad = [ads objectAtIndex:2];
Alternatively you could create an NSDictionary, if you really want to refer to them by a name, like this:
NSMutableDictionary *ads = [NSMutableDictionary dictionary];
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* thisad = [[[AdData alloc] init] autorelease];
NSString *keyName = [NSString stringWithFormat:#"ad%d", i];
[ads setObject:thisad forKey:keyName];
}
// get third ad
AdData *ad = [ads objectForKey:#"ad2"];
Cant be done Without using a C array, which would look like this:
AdData **ad = malloc(sizeof(AdData) * numberOfAds);
ad[1] = [AdData new];
// etc.
if (ad)
free(ad);
But I don't know how that would work because of how Objective-C classes are stored....
Local variable names are a purely compile-time concept. So you cannot do anything "dynamic" (i.e. at runtime) with it. The compiler is free to rename the variables and add or remove variables as it sees fit.
If you think about it, what is the point of dynamically manipulating local variable names? In order to use the dynamically-named variable again, you must either 1) explicitly refer to the variable name, in which case you have hard-coded the name (not so dynamic), or 2) dynamically construct the name again. If it's (1), then there is only a fixed set of variable names, so dynamic-ness is unnecessary. If it's (2), you're missing the point of local variable names (the whole point of which is so they can be referred to explicitly).

Return NSString from a recursive function

I have a recursive function that is designed to take parse a tree and store all the values of the tree nodes in an NSString.
Is the algorithm below correct?
NSString* finalString = [self parseTree:rootNode string:#""];
-(NSString*)parseTree:(Node*)currentNode string:(NSMutableString*)myString
{
[myString appendText:currentNode.value];
for(int i=0;i<[currentNode.children length];i++){
return [self parseTree:[currentNode.children] objectAtIndex:i] string:myString];
}
}
No, it is not.
You pass in #"" as the starting string. However, #"" is not an NSMutableString. This will certainly produce an exception when run.
As soon as you return, then that method stops and you don't execute any more. This means that you go through the first iteration of the loop, you'll stop. Forever.
You don't return anything if there are no children in the currentNode.
There's no such method as appendText:. You probably mean appendString:
Here's another question: Why do you need to return a value at all? You're passing in an NSMutableString and modifying it, so why not just always modify it in place and not bother with a return value? For example:
- (void) parseTree:(Node*)currentNode string:(NSMutableString*)myString {
[myString appendString:currentNode.value];
for(Node * child in [currentNode children]){
[self parseTree:child string:myString];
}
}
And then invoke this with:
NSMutableString * finalString = [NSMutableString string];
[self parseTree:aNode string:finalString];