6

Background

In Cocoa, Apple frequently makes use of the following paradigm:

[NSApplication sharedApplication]
[NSNotificationCenter defaultNotificationCenter]
[NSGraphicsContext currentContext]
[NSCalendar currentCalendar]

and so on.

They also will occasionally make use of a paradigm that I feel is far more legible when working with vast amounts of code.

NSApp //which maps to [NSApplication sharedApplication]

Goal

I'd love to be able to utilize this sort of global variable, both in my own classes, and in extensions to other classes.

MYClassInstance
NSDefaultNotificationCenter
NSCal /* or */ NSCurrentCalendar

and so on.

The "duh" Approach

#define. Simply #define NSCal [NSCalendar currentCalendar], but as we all know by now, macros are evil (or so they say), and it just doesn't seem like the right Cocoa way to go about this.

Apple's Approach

The only source I could find regarding NSApp was APPKIT_EXTERN id NSApp;, which is not exactly reusable code. Unless I'm mistaken, all this code does is define NSApp to be an id the world around. Unfortunately unhelpful.

Close, but not Quite

In my searches, I've managed to find several leads regarding "global constants", however things like this:

extern NSString * const StringConstant;

are unfortunately limited to compile-time constants, and cannot map to the necessary class method.

Bottom Line

I'd love to be able to roll my own NSApp-style global variables, which map to class methods like [NSNotificationCenter defaultNotificationCenter]. Is this possible? If so, how should I go about it?

Further Attempts

I'm trying to implement specifically the framework singletons in the following way:

MySingletons.h

//...
extern id NSNotifCenter;
//...

MySingletons.m

//...
+(void)initialize
{
    NSNotifCenter = [NSNotificationCenter defaultCenter];
}
//...

MyAppDelegate.m

//...
#import "MySingletons.h"
//...
//in applicationDidFinishLaunching:
    [MySingletons initialize];
    NSLog(@"%@", NSNotifCenter);
//...

However, this results in a compile-time error where the _NSNotifCenter symbol cannot be found.

Goal!

I'm currently working on an Objective-C class to encapsulate some of the framework singletons I've referred to in this question. I'll add the GitHub information here when I get it up.

Patrick Perini
  • 22,555
  • 12
  • 59
  • 88
  • I may have read too fast before posting my answer. Are you talking about doing this for your _own_ singletons, or for framework singletons? – jscs Dec 14 '11 at 02:14
  • Preferably both. Should there be a particular difference? – Patrick Perini Dec 14 '11 at 02:15
  • Well, you have direct access to the variable holding your own singletons, but not to those of framework classes. Will expand my answer. – jscs Dec 14 '11 at 02:16
  • 3
    This statement: "which maps to [NSApplication sharedApplication]" is wrong, and leading you down the garden path. NSApp is merely an id, as you point out under "Apple's Approach". Mentioning 'NSApp' in your code is __not__ invoking [NSApplication sharedApplication]; just accessing a global variable. – Graham Perks Dec 14 '11 at 03:07

2 Answers2

8

That's funny, I just made this suggestion on another question.

You just expose the variable that holds the singleton instance as a global itself. NSApp isn't actually mapping to a sharedApplication call. It's a regular old pointer; it was set up during the application launch process to point to the same instance that you would get back from that call.

Just like NSApp, you declare the variable for any file which imports the header:

extern MySingleton * MySingletonInstance;

in the header (you can use APPKIT_EXTERN if you like; the docs indicate that it just resolves to extern in ObjC anyways).

In the implementation file you define the variable. Usually the variable holding the shared instance is declared static to confine its linkage to that file. If you remove the static, the statement defines storage that is "redeclared" in the header.

Then, use it as you did before. The only caveat is that you still have to get your singleton setup method [MySingleton sharedInstance] called before the first time you use the global in order to make sure it's initialized. -applicationDidFinishLaunching: may be a good candidate for a place to do this.

As for creating pointers to framework singletons, you can just stash the result of [CocoaSingleton sharedInstance] in whatever variable you like: an ivar in a class that wants to use it, a local variable, or in a global variable that you initialize very early in your program via a function you write.

The thing is, that's not guaranteed not to cause problems. Except in the case of NSApp (or unless it's documented somewhere) there's really no guarantee that the object you get back from any given call to sharedInstance is going to remain alive, valid, or useful past the end of your call stack.

This may just be paranoia, but I'd suggest not doing this unless you can find a guarantee somewhere that the supposed singletons you're interested in always return the same instance. Otherwise, you might suddenly end up with a dangling global pointer.

Addressing your code, the declaration in your header doesn't create a variable. You still need a definition somewhere:

// MySingletons.h
// Dear compiler, There exists a variable, NSNotifCenter, whose 
// storage is elsewhere. I want to use that variable in this file.
extern id NSNotifCenter;

// MySingletons.m
// Dear compiler, please create this variable, reserving memory
// as necessary.
id NSNotifCenter;

@implementation MySingletons

// Now use the variable.
// etc.

If you're creating a singleton, you might want to glance at Apple's singleton documentation.

Community
  • 1
  • 1
jscs
  • 63,694
  • 13
  • 151
  • 195
  • I gave the framework singletons a shot, but I'm getting some errors. Could you check my updates? – Patrick Perini Dec 14 '11 at 16:10
  • Sure. You need to have a _definition_ of `id NSNotifCenter` somewhere (top-level of MySingletons.m). I've added a bit to my post to address that. Two concerns, though: prefixes in Cocoa are used for namespacing -- you shouldn't call this variable `NS...`. Also, I'm fairly certain you shouldn't be calling `+initialize` directly. The runtime will do it for you when you send a message to an instance of the class. If this class is _itself_ a singleton, just use your access method, `+sharedInstance` or whatever it's called. – jscs Dec 14 '11 at 18:19
  • When accessing `extern` variables from a class, does that cause the class to be called (thus invoking `initialize`?) I'm trying to see if I can't use the `initialize` method to set all of the variables, without having to have a specific setup call. – Patrick Perini Dec 15 '11 at 02:47
  • 1
    No, this variable access doesn't have any real relationship to the class. The variable just happens to be in the same file; it's not part of the class at all. – jscs Dec 15 '11 at 07:29
  • More information regarding as to why this variable must only be defined as extern rather than static as well can be found [here](http://stackoverflow.com/questions/7642304/objective-c-static-extern-public-variables). – Senseful Mar 30 '14 at 04:21
2

The existing discussion here was so intriguing that I did a little research and discovered something I'd never realized before: I can #import a header file from my own project into the project's .pch file (the precompiled header). This header file becomes automatically visible to all the other class files in my project with no effort on my part.

So here's an example of what I'm now doing. In the .pch file, beneath the existing code:

#import "MyIncludes.h"

In MyIncludes.h are two kinds of thing, categories and externs (the latter in accordance with Josh's suggestion):

extern NSString* EnglishHiddenKey;
extern NSString* IndexOfCurrentTermKey;

@interface UIColor (mycats)
+ (UIColor*) myGolden;
+ (UIColor*) myPaler;
@end

In MyIncludes.m we provide definitions to satisfy all the declarations from the header file. The externs don't have to be defined from within any class:

#import "MyIncludes.h"

NSString* EnglishHiddenKey = @"englishHidden";
NSString* IndexOfCurrentTermKey = @"indexOfCurrentTerm";

@implementation UIColor (mycats)
+ (UIColor*) myGolden {
    return [self colorWithRed:1.000 green:0.894 blue:0.541 alpha:.900];
}
+ (UIColor*) myPaler {
    return [self colorWithRed:1.000 green:0.996 blue:0.901 alpha:1.000];
}
@end

Except for the part about using the pch file to get magical global visibility, this is not really any different from Josh's suggestion. I'm posting it as a separate answer (rather than a mere comment) because it's long and needs formatting, and the explicit code might help someone.

(Note that there is no memory management, because I'm using ARC. The externs leak, of course, but they are supposed to leak: they need to live as long as the app runs.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • +1 for research. Your concern about leaks is unfounded, though. A leak only occurs if you lose the pointer to a chunk of memory without deallocating the chunk. In this case, the lifetimes of the pointer and the memory/object are identical. There's no leak. (Further, in the specific case of literal `NSString`s, there's never any _actual_ concern about leaks, because they are immortal.) – jscs Dec 24 '11 at 21:03
  • Under ARC, assignment retains. So saying `NSString* EnglishHiddenKey = @"englishHidden"` increases this string's retain count. But ARC has no mechanism for releasing external variables, the way it does for releasing instance variables; so there is in fact a kind of leak. But, as I say, I don't care, because this is not an instance but a class, and the lifetime of the class is the lifetime of the app. – matt Dec 25 '11 at 01:16
  • Are you sure that assignment of a _literal_ string causes `retain` to be sent? I'd be surprised if that were the case -- `retain` does nothing, and literal strings have a reference count of `UINT_MAX`. It's really not a leak, for many reasons: a) the lifetime of the reference and the object itself are identical, b) the memory isn't allocated at runtime, but compiletime, and c) that lifetime is the lifetime of your app. You only have a leak if you can't free memory that you've allocated _while your app is running_. The memory management of this code is correct under both ARC and MRR. – jscs Dec 25 '11 at 01:29
  • It's only a contingent fact that the strings in this example are literal strings. The technique I'm suggesting is intended to work for any kind of object. The OP was thinking of saying something like `UIApp = [UIApplication sharedApplication];` under ARC, there's a retain in that story. The fact that memory management of literal strings is special is a red herring. You may be right that I'm misusing the word "leak" slightly, but only in order to say exactly what you're saying - it isn't a "leak" in the "worry about this" sense. – matt Dec 25 '11 at 01:48
  • Besides, it's perfectly possible to use external variables in an instance, and in that case, if you don't manage the memory, there really _is_ a leak under ARC. What I'm trying to say is, "Yes, external variables can be tricky to manage under ARC, but in this case there's nothing to worry about." – matt Dec 25 '11 at 01:52