Ad

Our DNA is written in Swift
Jump

The Death of Global Variables

c0der asks:

I currently implement global variables by defining them within the AppDelegate.h file (outside of class definition) and then include this file in each ViewController.m that needs access to these variables.

This works just fine but I’ve noticed that after saving/restoring these variables as persistent data (NSUserDefaults), their values seem to change over time.

My question: is this approach the right way to handle globals without encapsulation? I use a lot of ‘C’ data types — int, char, etc. for my global variables (rather than NSInterger, etc.) so I’m not sure if there’s a better approach.

My short answer: DON’T. Globals are EEEEVILLLLLL. Why not create an engine class for your app that you use as shared instance. Define all your “globals” there.

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.


Categories: Recipes

29 Comments »

  1. Nicely explained. Good article.

    M.

  2. hi,
    I have a question..
    Should I run ‘Engine *myEngine = [Engine sharedInstance];’ in any function/procedure where I have to use it?

  3. Can you create simple example for NSDate?
    I’ve got some problems with it in different classes.
    Thanks,
    Igor.

  4. Yes, that’s one way how you can get a pointer to the Singleton in any other class.

  5. NSDate works the same as you would do a regular property.

    @interface Engine
    {
    NSDate *aDate
    }

    @property (nonatomic, retain) NSDate *aDate

    And a @synthesize in the @implementation.
    And a [aDate release] in the dealloc.

    That’s it.
    @end

  6. that helps me a lot :).
    thank you.
    About release. I added dealloc void to Engine.m. Hope it’s correct.

  7. Hi,
    I want share the dictionary of my two classes
    Can I use this tutorial?
    Can you help me?
    thanks

  8. I don’t understand what you Need.

  9. I have a dictionary in my class ( class A )
    With class A i load a UIPICKER and, the result, is the value ( added in the B class, the controller of the picker )
    I want insert the value of B class in the dictionary of A class
    Thanks

  10. Nice!!!

    You are showing me the way to share info between views (iPhone apps). But I’ve just learned half the way. Now I’m able to put data into variables, but I can’t take it back. In this example, how could I retrieve variable values from another class?

    Thanks a lot.

  11. awesome! you saved my day!!! 🙂
    Thanks for sharing

  12. Hey did you mean to type ” if (self == [super init])” instead of “if (self = [super init])” I think you may be missing an equal sign. but I could be wrong, let me know… GREAT ARTICLE!!

  13. No, a Single equal sign is intentional here.

  14. It comes up with an error when I’m using only one equals though?

  15. Not error, warning. In Xcode 4 do this

    self = [super init];

    if (self) …

  16. Thank you for sharing this code! It works and is very usefull.

  17. You sir made my life easy!! Cheers!!

  18. The best explanation of this concept that I’ve seen anywhere. Thanks!!

  19. Thank you soooo much!! OMG I was completely lost about common data between VCs until I saw this. You’re awesome. Just so you know. 🙂

  20. Hi,

    I have implemented your singleton, and every other one I ave found and still having problems with the sharing of a variable.

    I have two scenes and I am trying to keep track of the score between them. Both scenes use the same singleton but the value is different in both scenes.

    Can you see what I am doing wrong? Spent most of the day on this and still no luck.

    Cheers

    Shane

    .h file

    #import

    @interface User : NSObject {

    NSInteger OpponentScore;
    }

    @property (readwrite) NSInteger OpponentScore;

    //+ (User*)sharedUser;
    + (User *) sharedUser;

    – (NSInteger) getOpponentScoreValue;
    – (void) setOpponentScoreValue:(NSInteger)newVal;

    @end

    .m file

    #import “SingletonGameState.h”

    @implementation User

    static User *_sharedUser;

    – (id) init
    {
    OpponentScore = 0;

    return self;
    }

    + (User *) sharedUser
    {
    if (!_sharedUser)
    {
    _sharedUser = [[User alloc] init];
    }

    return _sharedUser;
    }

    – (NSInteger) getOpponentScoreValue
    {
    return OpponentScore;
    }

    – (void) setOpponentScoreValue:(NSInteger)newVal
    {
    OpponentScore = newVal;
    }

    Scene 1

    // Each time the opponent is destroyed
    User *user = [User sharedUser];
    [user setOpponentScoreValue:[user getOpponentScoreValue] + 1];
    [ScoreLabel setString:[NSString stringWithFormat:@”Score : %i”, [user getOpponentScoreValue]]];

    Scene 2
    // To show the score on the start page
    User *user = [User sharedUser];
    if ([user getOpponentScoreValue] > 0 ) {
    [ScoreLabel setString:[NSString stringWithFormat:@”Score : %i”, [user getOpponentScoreValue]]];
    }

  21. Check in debugger if the shared user object is indeed the same. If not then you have a concurrency problem and need to implement locking.

  22. I must say that I’ve been trying for days, no, make that a week, as to how to pass instance variables amongst various classes. Your example saved the day!

    Thank you, Art

  23. Great article! I’m developing on iPhone, and I’d like to add a simpler example to complement this:

    Here is the template for the .h file:

    //Singleton.h
    #import

    @interface SingletonManager : NSObject
    {
    NSDictionary* randomDictionary; //just using a dictionary for demonstrative purposes. You can make this a string or whatever you want.
    }

    + (Singleton*)sharedSingleton;

    @property (nonatomic, retain) NSDictionary *randomDictionary;

    @end

    Here is the template for the .m file:

    //Singleton.m
    #import “Singleton.h”

    static Singleton *sharedSingleton = nil;

    @implementation Singleton

    @synthesize randomDictionary;

    #pragma mark Singleton Method
    + (Singleton*)sharedSingleton
    {
    @synchronized(self)
    {
    if(sharedSingleton == nil)
    {
    sharedSingleton = [[super allocWithZone:NULL] init];
    }
    }
    return sharedSingleton;
    }
    @end

    To get the data, you can then use:

    Singleton *singletonManager = [Singleton sharedSingleton];
    [[singletonManager randomDictionary] description]; // example of getting the description

  24. Hi
    I have your example working in my app but I would prefer to use string variables rather than integer. I have tried various permutations and can build without errors but the retrieved text become numbers, could you please give this newbie some help?