Global Variables for Class Methods

2019-01-19 02:55发布

问题:

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.

回答1:

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.



回答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.)