I'm programming a game that has 40 levels and I'm storing all my level data in a .plist. This works great but now that I'm trying to implement multiple levels, I'm looking for a way to call a property on a singleton based on level without manually coding something like a switch/case. I would like to do something like the following:
This is the code I use to pull my data but as you can see, I've hard coded the "dictionaryLevel1Data" property.
int capacity = [[sharedDataManager.dictionaryLevel1Data objectForKey:#"typeWarrior"] intValue];
I would like to set a variable and have the property of the singleton called based on that like this which I know doesn't work. Notice how I'm replacing "dictionaryLevel1Data"
NSString level = #"1";
int capacity = [[sharedDataManager.[NSString stringWithFormat:#"dictionaryLevel%#Data", level] objectForKey:#"typeWarrior"] intValue];
Any suggestions on how I could accomplish this would be greatly appreciated. I can always go and manually setup a switch/case for each level but with 40 levels and 50+ different object types per level it would be rough, it seems like there must be a better way. I hate hard coding values and like to simplify my code with easy updates to variables that run through generic classes and methods.
why don't you do like this;
use 2 level data map;
//sharedDataManager.levelData is a NSDictionary that contains lots of NSDictionarys
NSDictionary *levelData = [sharedDataManager.levelData objectForKey:[NSString stringWithFormat:#"dictionaryLevel%#Data", level]];
int capacity = [levelData objectForKey:#"typeWarrior"] intValue];
If you want do it this way, then you can use Key-Value-Coding, to get a property value from a string. The accepted answer in this post shows how to use it (very easy): Objective-C get a class property from string
For your task it would look something like this:
int capacity = [[[sharedDataManager valueForKey:[NSString stringWithFormat:#"dictionaryLevel%#Data", level]] objectForKey:#"typeWarrior"] intValue];
You can use :
NSString level = #"dictionaryLevel1Data"
SEL methodSelector = NSSelectorFromString(level);
if ([sharedDataManager conformsToSelector:#selector(methodSelector)]) {
NSDictionary *levelData = [sharedDataManager performSelector:methodSelector];
// Do whatever you need here
}
I assume all dictionaryLevelData are synthesized and have getters as their name.
(Note that I did not compile it but it should work)
Related
I've realized that several of my list view controllers behave in the same way, and now I want to abstract them into a generic one called ListVC. The plan is to create an instance of ListVC when its needed, and pass into it all of the specific things needed for that instance of it.
For example, if I need a list of customers I would do something like:
ListVC *customersVC = [[ListVC alloc] init];
customersVC.tableArray = self.customersList
In ListVC, there are times when I need to know certain object's class type. In my example, the objects in the array self.customersList are from the class CustomerClass. I know I can convert strings to class names but that doesn't help when I have a method inside ListVC that needs to return the object's type. For example, in ListVC, this old non-abstracted method:
- (CustomerClass *)customerAfterFilter
should be something like:
- (self.objectClass *)objectAfterFilter
And I'd set objectClass after it was instantiated like so.
ListVC *customersVC = [[ListVC alloc] init];
customersVC.tableArray = self.customersList
customersVC.objectClass = [CustomerClass class];
How do I do something like this? Or if my approach is just wrong, please suggest the correct approach. I'm a beginner in programming.
You can't change the return type like that. What you can do is return an id, which is a pointer to an object of any class.
- (id)objectAfterFilter
EDIT:
Customer *c = [customersVC objectsAfterFilter];
[c duCustomerStuff];
Alternatively:
[((Customer *)[customersVC objectsAfterFilter]) doCustomerStuff];
I have dozens of NSStrimgs that when the app loads I want to all be set to the same set. All of them. How can I do this without typing out every single one? Is there a shortcut method?
Thanks.
Also the problem is that Josh isn't specific enough about how he's using his dozens of strings... I think this would be better:
NSMutableArray *stringsArray = [NSMutableArray arrayWithCapacity:1];
NSString *tempStr = #"My unique string"; // Thanks Sven!
// Say you want a dozen strings
for (int i = 0; i < 12; i ++) {
[stringsArray addObject:tempStr];
}
// Now you can use them by accessing the array
[self doSomethingWithString:[stringsArray objectAtIndex:8]];
Instead of having dozens of strings that have the same value, could you make a single static global string and reference that? If you need to change it to separate values later, use instance variables that are initialized to the global string.
This sounds like your model is not very good at all. Since you want to initialize all of your strings to the same value they are obviously related and probably should be modeled as an array like iPhoneDevProf described. That makes other things a lot easier too, you can move other code that is repeated for every string into a loop.
If the value is known when you are compiling the code AND it is not going to change after subsequent application sessions then you can use a simple #define.
#define MY_DEFAULT_STRING #"THE DEFAULT STRING"
Now all you have to do is the following.
{
NSString *myString1 = MY_DEFAULT_STRING;
NSString *myString2 = MY_DEFAULT_STRING;
....
NSString *myStringN = MY_DEFAULT_STRING;
}
If all the strings are in the same code file, just put the define at the top. If the strings are in separate code files, then it could be put into your precompiled header. Having a constants file is usually better.
Using constant extern NSString would probably be more correct, but this is simple and easy to do.
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 am building my first iPhone/Obj-c app and I have a large amount of data-holding subclasses that I am passing into a cite function. To the cite function these objects are anonymous and I need to find a way to access all the variables of each passed object.
I have been using a pre-built NSArray and Selectors to do this but with more than 30 entries (and growing) it is kind of silly to do manually. There has to be a way to dynamically look up all the variables of an anonymous object.
The obj-c runtime run-time docs mention this problem but from what I can tell this is not available in iPhone OS. If it is then I don't understand the implementation and need some guidance. A similar question was asked before but again I think they were talking about OSX and not iPhone.
Any thoughts?
-(NSString*)cite:(id)source {
NSString *sourceClass = NSStringFromClass([source class]);
// Runs through all the variables in the manually built methodList
for(id method in methodList) {
SEL x = NSSelectorFromString(method);
// further implementation
// Should be something like
NSArray *methodList = [[NSArray alloc] initWithObjects:[source getVariableList]]
for(id method in methodList) {
SEL x = NSSelectorFromString(method);
// Further implementation
}
The runtime is the same on the Mac as it is on the iPhone. If the other question does what you're looking for, then it should work. If it doesn't, file a bug.
In the meantime, given a Class, you can retrieve a list of all of its selectors using the class_copyMethodList() function:
unsigned int numMethods = 0;
Method * methods = class_copyMethodList(sourceClass, &numMethods);
NSMutableArray * selectors = [NSMutableArray array];
for (int i = 0; i < numMethods; ++i) {
SEL selector = method_getName(methods[i]);
[selectors addObject:NSStringFromSelector(selector)];
}
free(methods);
It's certainly possible to do this through the Objective-C runtime functions, but it's probably not the right way to go about it. Since you're creating the objects passed into the cite method, just have them each implement a protocol that cite can use to extract whatever information it needs.
Something like the Key-Value Coding protocol will probably do what you want:
http://developer.apple.com/iphone/library/documentation/cocoa/Conceptual/KeyValueCoding/KeyValueCoding.html
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()