The topic of global variables is a highly philosophical and emotional one as I should later see. Programming teachers usually try to dissuade their students from using them wherever possible, but newcomers still insist on using them. And then there is the rebel amongst seasoned programmers who rides to the rescue of global variables. “No! Globals are not evil! It’s evil people that abuse them and kill programs with them.”
My usual approach is quickly steer the topic to something completely different. Did I mention that I am getting married in just 1 week?
Real Global Variables
Ok, but seriously, yes, you can create global variables if you absolutely insist. You can do this in C like this:
int myGlobalVar; // local declaration |
And in all other classes/modules that you want to be able to access it you go:
extern int myGlobalVar; // externally linked |
This external declaration you could pack into a header and #import this header everywhere you want to access this global variable. Only the declaration really reserves the memory for the variable. The “extern” keyword tells the linker (which runs after the compiler) to resolve all external references to point to the real declaration.
Ok, now, my son, I have shown you the pack of cigarettes. Will you please promise me not to smoke them in secret?
I generally call global variables evil because they tempt you with ease of use and seduce you away from encapsulated and easy to maintain code. The main reason being that only you – the original programmer – know what all those variables mean and where the declaration is buried.
Generally you don’t want to be dependent on such knowledge, you want clear cut pieces where you group together stuff that belongs together logically and bundle in the functions to work on this data. This concept is called “encapsulation” and is core to the principle of OOP (Object Oriented Programming)
“All in” Application Delegate
When I started out with trying to adhere to the Model-View-Controller paradigm I though this means that you put all data operations into the app delegate. Getting to the app delegate is simple enough, you only need to do it like this:
MyAppDelegateClass *appDelegate = [[UIApplication sharedApplication] delegate]; [appDelegate someMethod:..]; |
Again, very tempting. You feel much smarter because you don’t need global variables, but then you find yourself constantly copy-pasting the first line of the example because from everywhere you are needing to call methods and access data that you put in the app delegate. My first released apps are still full of these until I can find time to go back in and fix that.
Shared Instances
The cleanest, most professional method I have found on my travels is to use so-called shared instances. You basically create a class method that instantiates a single instance of the class (aka a “singleton”) on the first call and returns this instance on subsequent calls.
Here is an example showing an Engine class you might find in a board game. Note that you still can use c-style arrays if you so choose, but it’s all nicely packaged into a class.
Engine.h
#import @interface Engine : NSObject { NSUInteger board[100]; // c-style array } + (Engine *) sharedInstance; - (NSUInteger) getFieldValueAtPos:(NSUInteger)x; - (void) setFieldValueAtPos:(NSUInteger)x ToValue:(NSUInteger)newVal; @end |
Engine.m
#import "Engine.h" @implementation Engine static Engine *_sharedInstance; - (id) init { if (self = [super init]) { // custom initialization memset(board, 0, sizeof(board)); } return self; } + (Engine *) sharedInstance { if (!_sharedInstance) { _sharedInstance = [[Engine alloc] init]; } return _sharedInstance; } - (NSUInteger) getFieldValueAtPos:(NSUInteger)x { return board[x]; } - (void) setFieldValueAtPos:(NSUInteger)x ToValue:(NSUInteger)newVal { board[x] = newVal; } @end |
To test this new class we simple put the #import into our app delegate and a couple of lines:
// at the top: #import "Engine.h" Engine *myEngine = [Engine sharedInstance]; [myEngine setFieldValueAtPos:3 ToValue:1]; NSLog(@"pos 3: %d",[myEngine getFieldValueAtPos:3]); NSLog(@"pos 2: %d",[myEngine getFieldValueAtPos:2]); |
You can see that you don’t need to alloc-init the class, just always retrieve the sharedInstance pointer. Behind the scenes this class method will create the instance on the first call and save it in a static variable. Static variables retain their value indefinitely.
I am told that you don’t need to worry about the release because all of the app’s memory will be cleaned up when it quits anyway. But if you want to be extra-nice you can put a release for the sharedInstance into the dealloc of your appDelegate. Though I found that the appDelegate’s dealloc never actually gets called anyway. I suspect that is because the iPhone OS considers it quicker to just kill the app and free up all of it’s memory in one go.
You can also pack all other engine-related methods and data variables into this Engine class. Just not anything that has to do with displaying it. This makes the engine reusable. You might create variants of your app with totally different display paradigms but because the Engine class stands by itself you can reuse it.
In summary I am recommending that you don’t use any technique that will cause you more work in the future. You can mix and match techniques as you choose once you are a “grown up developer” and understand what you are doing.
Until you do, please refrain from using global variables and putting it all into the poor app delegate, but instead make shared instances your method of choice for globally accessible data.
No comments:
Post a Comment