I have an Array comprised of further sub-Arrays
I want to display a summary of the contents of the sub-arrays into a TableView with the count of occurrences of each entry. I have determined that the best and easiest way is through a NSMutableDictionary as an intermediate step.
I declare the dictionary in my implementation
#implementation ReviewViewController
{
NSMutableDictionary *dict;
}
and down in my methods I initialise and use it like so:
dict = [NSMutableDictionary new];
[dict setObject:[Observation entryCount] forKey:[observedItem species]];
The swapping of key and object is deliberate as its the count I want. I'm using the fact that values are retained, but keys are overwritten, so if i swap them round i get the curation for free.
It works!, but every time the method is invoked, the Dictionary is clobbered by the re-initalization so I only get the last thing entered. Anywhere else and it falls out of scope.
if I pass it as an arguement in the method name instead, I get the message "Local declaration of 'dict' hides instance variable ". The code is already an irreducable set of parts, so
so, where is the correct place for the instantiation?
I'd love to make my contribution here as a meaningful thanks, but before I can do that I'm going to look silly with such questions.
You would usually initialise it in your init method. For a view controller this is usually the following:
- (id)initWithNibName:(NSString*)nibName bundle:(NSBundle*)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
dict = [NSMutableDictionary new];
}
return self;
}
I don't understand what you mean by "Anywhere else and it falls out of scope.". It's an instance variable. It is therefore in scope in any method in the class.
By the way, I would name your instance variables with an underscore prefix, e.g. _dict. This is common convention and it helps you to remember when using it that it's an instance variable.
Related
I am having two arrays, Namely
NMutableArray* first;
NMutableArray* second;
Now I am copying first object to the second array like
for (int i=0;i<first.count; i++)
{
[second addObject:[first objectAtIndex:i];
}
This is ok. I don't know how to access the value of the First Array. I tried like this ,
[second addObject:[[first objectAtIndex:i]name]];
I want to get the name value which is in the first object of first array. I tried using the above line, it is showing some warning. Please help me
Assuming you started with an array like this:
NSArray *array1 = #[#{#name : #"Fred"},
#{#name : #"Bill"}];
You could create a second array that contains the value of a given property of each element of the first array as follows:
NSArray *array2 = [array1 valueForKey:#"name"];
If you then logged the second array...
NSLog(#"%#", array2);
...the resulting output would be
2012-04-18 16:26:11.226 ExampleRunner[23320:707] (
Fred,
Bill
)
EDIT
Note that this will work regardless of whether the objects in the first array are instances of NSDictionary as shown in the example above, or instances of a class or classes that have a name property or instance variable (or an _name instance variable, for that matter). For more information on how and why this works, see the documentation for the NSKeyValueCoding informal protocol:
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/Reference/Reference.html
The brackets are currently in the wrong place:
[second addObject:[[first objectAtIndex:i] name]];
Updated Answer:
Again, I think you should split stuff out into easy to parse lines of code:
for (id theObject in first)
{
// without an actual type, I still think the compiler might
// throw a warning on this next line of code;
// but maybe RJR III is correct and it won't warn.
// I didn't check.
NSString * nameOfObject = [theObject name];
if(nameOfObject)
{
[second addObject:nameOfObject];
}
}
Notice that I do some error checking in here as well (i.e. making sure the name is not nil).
Original Answer:
You're getting a warning because the compiler doesn't know what kind of custom object is being fetched from your call to "[first objectAtIndex: i]". In other words, it doesn't know what kind of object you're trying to get the "name" of.
Cast it to the right type and you'll get rid of the warning.
Or even better, split that one line of multiple things happening at once into two or three lines of code and make your code more readable in the process.
Could some one tell me why my array is out of scope?
Here's my class:
// Paper.h
#interface Paper : NSObject {
NSMutableArray* items;
}
#property (retain) NSMutableArray* items;
// Paper.m
#import "Paper.h"
#implementation Paper {
#synthesize items;
}
// ParserUtil.m
#implementation ParserUtil {
+(Paper*) parsePaper:(NSString*)file {
...
Paper* paper = [[[Paper alloc] init] autorelease];
// does the following line is the best practice?
paper.items = [[[MutableArray alloc] init] autorelease];
Item* item = ...; // create item instance
[paper.items addObject:item];
return paper;
}
// call the parser method
...
Paper* paper = [[ParserUtil parsePaper:#"SomeFile"] retain];
// when run to this line, the paper.items is out of scope
// seems all the items in the array are dispear
NSMutableArray* items = paper.items;
...
Could someone point out what is wrong here?
Many thanks!
It isn't.
An object cannot be out of scope, because objects do not have scope. What they can be is unreachable, which is what happens when you don't have any variables holding the object's pointer.
Variables can be out of scope. You can only use a variable within the same scope in which you declared it; you can't begin a compound statement, declare a variable, finish the compound statement, and use the variable, and you can't declare a variable in one function or method and then use it in a different one.
You said in your other question that it's the debugger telling you the variable is out of scope. This means one of two three things:
The variable really is out of scope. Move the variable or move the code that uses it, or just interrupt the debugger earlier (with a breakpoint, if necessary).
The debugger is just being stupid. This happens a lot. Try the po command or sprinkle your code with NSLog statements instead.
You're trying to examine a property-access expression. A property-access expression, by definition, must send an accessor message, which may have side effects; for that reason, the debugger won't do that just for you hovering over the expression, because that's too easy to do by accident. You must use the po command in the Debugger Console to send the accessor message and print the description of the result.
i'm in a bit of a situation here...
i am passing a string to a function and in that function i need to create an array whose name is the value of the string.
Say, for example: -(void) function : (NSString *) arrayName; //let arrayName = #"foo";
In this function I need to create an array named "foo" i.e the value of the passed parameter.
Can anyone help please :|
Thanks in advance ;)
Arrays don't have names. Variables have names, but variables are local to their scope, so once you leave the scope of that method, having a variable named "foo" is pointless; you can name the variable whatever you want and it will work just fine. Ex:
- (void) function:(id)whatever {
NSArray * myVariable = [NSArray arrayWithStuff....];
//use myVariable
}
What are you really trying to do?
That is not possible in Objective-C, but you can use e.g. a dictionary that maps a string to an array.
E.g. assuming something like the following property:
#property (readonly, retain) NSMutableDictionary *arrays;
... you can store an array by name:
- (void)function:(NSString *)arrayName {
NSArray *array = [NSArray arrayWithObjects:#"foo", #"bar", nil];
[self.arrays setObject:array forKey:arrayName];
}
... and access it like so:
NSArray *array = [self.arrays objectForKey:arrayName];
C is a compiled language where any source code names (for variables, functions, etc.) are not available at runtime (except for perhaps optionally debugging, -g). The Objective C runtime adds to this the ability to look up Obj C methods and classes by name, but not objects, nor any C stuff. So you're out of luck unless you build your own mini-language-interpreter structure for reference-by-name. Lots of ways to do this, but simple languages usually build some sort of variable table, something like a dictionary, array, or linked-list of objects (structs, tuples, etc.) containing string name, object pointer (maybe also type, size, etc.).
I have defined a constants class within my iphone program using the 'extern' and 'const' keywords as in the example described in:
Constants in Objective-C
At this point, I am trying to initialize some string constants from the contents of a plist file, instead of being defined right in the class, e.g., instead of having:
// Constants.m
NSString * const MyConstant = #"a constant";
I would like to have it initialized somewhere from the plist file. So far, I have done a test using the static +(void)load method, but I am not completely happy about it, e.g.:
// Constants.m
NSString * ALERT_QUIT_TITLE;
#implementation Constants
+ (void)load {
// this controller contains all the strings retrieved from the plist file
LabelsController *labels = [LabelsController instance];
ALERT_QUIT_TITLE = labels.alertQuitTitle;
}
#end
Using a log call I can verify that the load code gets called early in the app startup, even before the AppDelegate constructor. However, two things I see not good in this approach:
I have to remove the 'const' keyword, otherwise I get a compile error since I am trying to initialize a variable that is defined as constant
I get some sort of warning message about the autoreleased pool:
*** _NSAutoreleaseNoPool(): Object 0x50b330 of class NSPathStore2 autoreleased with no pool in place - just leaking
Stack: (0x905caf0f 0x904d8647 0x904e039f (etc)
I guess I could use a direct call to the Labels controller to retrieve the label, but I would like more to treat it like a constant having all the maint. advantages it provides.
Which would be the correct (recommended) way to initialize a constant from an external source, like in this case a plist? Hope you can help, I have lost a good few hours trying to resolve this!
Thank you in advance.
If you initialize from a plist file, then you do not have a constant. And you should not define it as such.
I am guessing what you want is to be able to treat this value as if it was a constant? And that can be achieved using lazy initialization instead.
NSString* AlertQuitTitle()
{
static NSString* title = nil;
if (title == nil)
{
LabelsController* labels = [LabelsController instance];
title = labels.alertQuitTitle;
}
return title;
}
Is there a good reason as to why you do not use the NSLocalizedString() macro to fetch the alert quit title?
The warning
As the warning states, you are executing the +load method outside of an auto release pool. Meaning that all calls to autorelease just leak memory. You can fix your method like this:
+ (void)load
{
// this controller contains all the strings retrieved from the plist file
NSAutoreleasePool* pool = [NSAutoreleasePool new];
LabelsController *labels = [LabelsController instance];
ALERT_QUIT_TITLE = labels.alertQuitTitle;
[pool release];
}
I would suggest using the NSUserDefaults method instead for storing data.
Load is called far too early in the process for most purposes. Even initialize is fairly early. As you've noted, there is no autorelease pool setup, so any use of it (which is quite hard to avoid) will give you warnings and possible leaks.
A better way to do it is to forget the constant entirely, and write LabelController alertQuitTitle to lazily initialize its database and cache its answer. Something like this (untested, uncompiled).
+ (NSDictionary*) labelStrings;
{
static NSDictionary* strings = nil;
if ( !strings ) {
// Allocate and laod and keep ownership of the NSDictionary
}
return strings;
}
+ (NSString*) alertQuitTitle
{
static NSString* alertQuitTitle = nil;
if ( !alertQuitTitle ) {
alertQuitTitle = [[LabelController strings] objectForKey:#"alertQuitTitle"];
}
return alertQuitTitle;
}
If you really want, you can convert alertQuitTitle into a macro and use that to easily create dozens of methods.
In your other code, if you really want to, you can write a method that caches the answer as well, but thats fairly pointless, instead just use [LabelController alertQuitTitle].
You can, if you prefer, use a singleton, but there is not much point even creating a single instance of LabelController unless you have other things for it to do - any data it needs can be stored as static variables. A singleton would be more inline with typical Cocoa behaviour though. Either way, the same technique will work.
To directly answer your question, it looks like you're calling load before an NSAutoreleasePool has been set up. Every thread needs its own NSAutoreleasePool; your main thread's NSAutoreleasePool is set up in main.m, which you can see if you open up that source file.
I usually initialize my application's globals in my App Delegate's init method.
But this looks like unnecessary optimization, and it's creating problems as a result. You should consider using string resources for something like this. See NSBundle localizedStringForKey:value:table:, and NSLocalizedString()
I need to check specific positions in an NSArray to see if they have already been initialized, but I am having trouble. I tried to do the following, but it causes my application to crash!
if ((NSMutableArray *)[arrAllBlocks objectAtIndex:iLine] == nil)
{
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
What is the best to do this? I already tried some methods like isValid, and things like that!
You have a few options here:
Option 1: Pre-fill the array with instances of NSNull, and then use the code given by Dave DeLong in his answer.
Option 2: (Similar to #1) pre-fill the array with instances of NSMutableArray, and then have no extra code at all. (If you're going to pre-fill, you may as well do this).
Option 3: Do not pre-fill the array, but insert items dynamically as required. This will be almost identical to a pre-fill if the first iLine is near the maximum:
while([arrAllBlocks count] <= iLine)
{
[arrAllBlocks addObject:[NSMutableArray arrayWithCapacity:0]];
}
NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];
Option 4: Use a dictionary to maintain the list of NSMutableArrays:
NSString *key = [NSString stringWithFormat:#"%d", iLine];
NSMutableArray *columnArray = [dictAllBlocks objectForKey:key];
if (columnArray == nil)
{
columnArray = [NSMutableArray arrayWithCapacity:0];
[dictAllBlocks setObject:columnArray forKey:key];
}
[columArray insertObject:newBlock atIndex:iColumn];
How to choose:
If the maximum value for iLine is not enormous, I would go with option #2. A handful of NSMutableArrays initialized to zero capacity will take up very little memory.
If the maximum value for iLine is enormous, but you expect it to be accessed sparsely (i.e., only a few values of iLine will ever be accessed), then you should go with Option #4. This will save you from having to fill an NSMutableArray with objects that never get used. The overhead of converting the string-value key for the dictionary will be less than the overhead for creating all of those blanks.
If you're not sure, try out each option and profile them: measure your memory usage and the time required to execute. If neither of these options work, you may have to explore more complex solutions, but only do that if it turns out to be necessary.
A note of caution:
The original code that you posted has a memory leak in the following line:
[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
The NSMutableArray objects that you initialize here are never released. When you call [[NSMutableArray init] alloc], a brand new object is created (with a reference count of one). The insertObject method then adds that new object to arrAllBlocks, and retains it (increasing its retain count to 2). Later, when you release arrAllBlocks, the new array will be sent a release message, but that will only reduce its retain count to one again. At that point, it will stick around in RAM until your program exits.
The best thing to do here is to use [NSMutableArray arrayWithCapacity:0] instead (as I have done in my examples). This returns a new NSMutableArray, just the same as your code did, but this instance has already been autoreleased. That way, arrAllBlocks can take ownership of the new object and you can be sure that it will be released when appropriate.
You can't. NSArray (and its subclass NSMutableArray) do not allow you to insert nil into the array. That's clearly outlined in the documentation.
If, for some reason, you need to have "empty" values in an array, then you should insert [NSNull null] instead and test for that. From the docs: "The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values)."
UPDATE:
This means you could change your code very simply to this:
if ([[arrAllBlocks objectAtIndex:iLine] isEqual:[NSNull null]]) {
[(NSMutableArray *)arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = (NSMutableArray *)[arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
To check for NSNull you can simply compare against the pointer, since it's a Singleton:
if ([NSNull null] == [arrAllBlocks objectAtIndex:iLine]) {
[arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = [arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
I also removed the unsightly casts. Casting is rarely necessary in Objective-C. It usually just adds noise, and can hide real bugs. Since you're experiencing crashes, it's worth removing the casts from this code and listen to what the compiler has to tell you about it.
Telling the compiler to ignore warnings for a piece of code does not make the underlying problem with it go away!