I am working my way through some Objective-C code that I did not write and have found a variable declaration style that I am unfamiliar with. Can anyone tell me the scope of the variable 'myVar' in the class implementation below? Note that this appears in the '.m' file and not the interface declaration.
#implementation MyClass
#synthesize ivar1, ivar2;
NSString* myVar; // <- What is the intent?
- (id)init {
...
#end
To me the intention appears to be similar to that of a member variable. What are the advantages of declaring a variable in this way instead of using an ivar in the #interface declaration?
It's just a plain old global variable. There's only one instance of it, and it can be accessed by any code within the same file translation unit (the final file you get after running the preprocessor). Other translation units (that is, other .m files) can also access that global variable, but in order to do so, they need to use an extern statement:
extern NSString *myVar;
extern says "this is the name of a global variable, but it's defined in a different translation unit". The linker resolves all of the extern declarations at link time.
a poorly named global variable...
I'm not too experienced in ObjC but I'd say that is a global.
Related
I understand that placing the word extern before a variable declaration in a header file declares the existence of a global static variable without initialising it. I also understand that if I import the file containing the extern variables, I can reference them without a class/file name. But where does one define them and their values?
What I am trying to do is create a class of constants with global constants that I want to use throughout an iOS application's code.
Does one put them inside the interface like this?
Example.h
#import <Foundation/Foundation.h>
#interface Constraints : NSObject
{
extern NSString * const PREFS_NAME;
}
Or does one put then outside of the interface like this
Example.h
#import <Foundation/Foundation.h>
extern NSString * const PREFS_NAME;
#interface Constraints : NSObject
{
}
Then in the implementation .m file how would one initialise the extern values?
Inside the implementation area like this?
Example.m
#import "Constraints.h"
#implementation Constraints
/**PRefecences name for the application**/
const NSString * PREFS_NAME = #"MyApp_Prefs";
#end
Or do initialise them outside of the implementation area like this:
Example.m
#import "Constraints.h"
/**PRefecences name for the application**/
const NSString * PREFS_NAME = #"MyApp_Prefs";
#implementation Constraints
#end
Or do I provide them their initial values in a constructor? or some arbitrary a static style method with + in front of it i.e. +(void) setAppConstraints;
I have tried several combinations, but always run into errors, such as "Redefinition of 'xVariable' with a different type". Or a something about "extern does not have an initialise interface" (or something like that, I forget). So I want to know how to declaire and initialise them properly to form the same role as public static final variables in Java.
Also what are the limits of the extern command? I know I can extern an NSInteger or NSString, but what about NSArray?
I am asking this question because there seems to be to much misleading, or incomplete, information regarding the use of extern in Objective-C. Many of the answers seem speculatory. My hope is for this question to be a good resource not only for me, but to limit further similar questions about the basics of extern.
You define it's value in the file inside which it's declared, which in your case is Example.m; You can still re-assign this variable, so the declaration in Example.h would look like this:
extern NSString * PREFS_NAME;
This way every file that imports Example.h has access to this variable. The equivalent of public static final in Objective-C is const. If you also want it to be public you should make it be a class instance variable, but in this case you don't need it because it's already accessible everywhere. So in this case it would be:
// .m file
NSString* const PREFS_NAME = #"MyApp_Prefs";
// .h file
extern NSString* const PREFS_NAME;
Also notice that const NSString* is different from NSString* const. The latter is a const pointer to NSString. The former hasn't sense even if it's a correct syntax. In Objective-C the const qualifier doesn't affect objects, instead there are mutable and immutable classes. It would have sense in C++ meaning that you can use just const methods on the instance.
extern is used to signal the compiler that you will be using a variable or a function that is defined in another compilation unit.
When you say extern const NSString *PREFS_NAME, you're saying "Replace all references in this compilation unit to PREFS_NAME to the variable PREFS_NAME as it is defined in another file." So when you try to assign PREFS_NAME in your .m, all you're doing is trying to assign a variable that, though it has a name, it doesn't exist. Declaring a variable extern is only a declaration of a variable or function, not a definition of that variable or function. It lets the compiler know that the name is in use, and that the linker will take care of what to do with it, but even if you provide a type here, it doesn't actually set aside space for the variable, it's expecting the space to be set aside in the compilation unit that's actually defining the variable.
You compile three or four different source code files together, three of them may declare:
extern int buffer[];
And one may declare
int buffer[BUFSIZE];
In its global scope, and the linker's job is to resolve the three declared references to extern buffer to the fourth's actual definition of the buffer.
extern is to C variables and functions much as #class is to Objective-C classes, it's a forward declaration, a promise to the compiler that you don't have to freak out when you see a name that's undefined here, because the linker will answer whatever lingering questions you may have.
#interface Foo : NSObject
{
extern int gGlobalVar;
int i;
}
-(void)setgGlobalVar:(int)val;
#end
#implementation Foo
-(void)setgGlobalVar:(int)val
{
i = 5;
NSLog(#"i = %i", i);
gGlobalVar = val;
}
#end
I can declare i in interface and use it in implementation without any errors. But I cannot declare a variable of the type extern in interface. Why is this so? Why do I get an error which says that: "Type name does not allow storage class to be specified"?
Short Description:
The bracketed section of a class's #interface OR #implementation is only for declaring instance variables (aka "ivar"). The extern keyword is only for use with global variable declarations (or functions, but that's another topic.)
Therefore, you cannot declare an extern ivar.
Gritty Details:
Variables are first declared, and then defined. This distinction is typically blurred for variables in local scopes, as a locally declared variable without an explicit definition will often be allocated and given a default value by the compiler.
Global variables are potentially available in any scope, provided that scope knows the global exists. That's where the keyword extern comes in -- it declares that the global variable exists, and was defined elsewhere. This is only useful when you want to access a global variable in different code files.
Best Practices: Your book has some code that declares an extern variable in an implementation file (e.g. ".m" files, etc.)... that can work, but it's a bad practice because you're making potentially bad assumptions about whether that global actually has a valid definition elsewhere. (But, fancy compilers will discover this type of error.)
Instead, the best practice is to declare an extern variable once in a header file, have an accompanying implementation file that's dedicated to defining the externs in that header, and then include that header in other implementation files that want to use that global variable.
I've noticed that I get compilation errors if I place certain declarations in certain places in my header file. I've put comments into the code as to where I think certain things go; are they correct?
#interface Level : CCNode {
//Instance variables?
PlayBackgroundLayer* playBGLayer;
PlayUILayer* playUILayer;
PlayElementLayer* playElementLayer;
}
//Static methods?
+(void) InitLevel: (int) levelNumber;
+(Level*) GetCurrentLevel;
//Property declarations for instance variables?
#property (nonatomic, retain) PlayBackgroundLayer* playBGLayer;
#end
//Static variables?
Level* currentLevel;
PlayTilemapLayer* playTilemapLayer;
You are correct on all counts except one. Your last variables are not static variables, they are global variables. Static variables are simply variables that are declared with the static keyword and they mean something a little different than other languages. They aren't class variables, they are variables that are visible only to the file that they are declared in, and only then in the scope that it was declared (if you declare it inside a function other functions won't see it). However, as you would expect, they are only declared once regardless of how many instances you have. If you declare something outside an interface without the static keyword as you did, other classes will import them. However, this is not the ideal way to accomplish this (you might get redefinition errors if more than one class imports this header).
Also, one caveat, is that properties don't need to have an explicit backing variable (the compiler will create one for you if you use the #synthesize keyword), but of course if you desire one there is nothing wrong with it.
Finally, you should note that the only reason that your static methods class methods are not instance methods is because they start with a plus (+) character as opposed to a minus (-) character.
Instance variables usually don't need to be declared explicitly. They're created when you #synthesize the property. If you do want them, though, the (new) correct place* is at the top of the implementation block:
#implementation Level
{
PlayBackgroundLayer* playBGLayer;
PlayUILayer* playUILayer;
PlayElementLayer* playElementLayer;
}
Those aren't static methods, they're class methods, but, yes, that's where you declare them. Some people like to put #property declarations before the class methods, but that's a matter of opinion. Instance methods go after both of these, although technically speaking the order doesn't matter -- that is, the compiler doesn't care, it's just a matter of readability.
Those top-level variables need to go somewhere other than a header file, though. If you put them there you will get compilation errors because every file that imports the header will appear to be re-declaring storage for those variables, which isn't allowed.
Ususally you put such variables into a .m file. If you want them to only be visible from there, you would use static. If you want them visible from other files that import the header, you leave static off and declare the variable as extern in the header:
extern Level* currentLevel;
This lets the compiler know that the storage for the variable is reserved elsewhere.
*See "Class Interface" in TOCPL.
int helloness;
#interface test : NSObject
#end
vs
#interface test : NSObject{
int helloness;
}
#end
Do I understand that following are true and the only meaningful differences between the above two blocks:
in both blocks, the implementation of test.m can use helloness variable internally, like an ivar
in the first block, helloness will exist for any class that imports this .h but is otherwise private only to test.m in the second block
In the first block, is this technically what is considered a "global variable" in that any class that imports this will have access to the same contents of helloness?
What happens if multiple header files have a declaration for helloness and you import them all?
Similar to this, consider this implementation:
#implementation AClass
int food=5;
Here, food acts like an internal iVar, even though it was not declared in any #interface ?
In your first example, helloness is a global variable. It can be seen by any file which imports that header. If you include multiple headers which also declare an int helloness variable, I believe you'll get a warning from the compiler, and all of them will point at the same memory location. If you include another header which declares a helloness of type other than int, I believe you'll get a compiler error.
In the second example, helloness is an instance variable (ivar). Its value (memory location) is specific to each instance of AClass. (Anything can access it: e.g. AClass *instance = [[AClass alloc] init]; instance->helloness = 7; However, direct access to ivars is generally avoided in ObjC -- we use accessors and/or properties instead.)
In the third case, food is still a global variable, but its visibility is restricted to the implementation file it's declared in. Any instance of AClass, as well as any other classes or categories or functions implemented in the same file, can reference food, and all those references are to the same memory location.
In your first example, helloness is a global variable. In your second example, it's an instance variable.
There can be only one global variable with a given name in your program. There is a copy of an instance variable for each instance of your class that's created during your program's execution. They're not semantically similar at all.
Having a global variable in a header file, as I presume you are doing in the first example since you refer to #importing it, is probably a bad idea. If it's not a tentative definition like yours is (for example if you instead had int helloness = 12;), you'll end up with multiply defined symbol errors at link time.
In your last example, food is still a global variable, but since it's likely to be in an implementation file (rather than a header), you probably won't run into any multiply defined symbol errors. It won't work like an instance variable, though - it's still a global variable.
By reading this post, It looks like certain rules should be considered when declaring global variables.
So I tried declaring global variables in the .m file. However, the code sense seems not happy to do this for me. For example, say I already have 2 lines in the .m file:
#implementation ViewController
#synthesize abc, xyz;
Now I want to add "BOOL isChecked;".
If I type this below "#synthesize" (or just between #implementation and #synthesize), the code sense actually suggests me to input "bool" (lower case) as I am typing "BOOL". If I type "BOOL" above "#implementation", it would suggest "BOOL" successfully.
Surely, the global variable is part of this class which means it should be inside the implementation. I am not sure why it doesn't like to let us do this.
This makes me feel that Objective-C doesn't like us to declare global variables below #synthesize. But my question is WHY? What I feel is that there may be a reason or Apple made a bug here.
Surely, the global variable is part of this class which means it should be inside the implementation. I am not sure why it doesn't like to let us do this.
Global variables aren't part of a class. Sure, you can put them inside an #implementation block, but they're really not a part of the class -- they're global -- so they don't really belong there.
Objective-C doesn't have class variables like Java or other languages do. You can fake them with global variables and class methods that access those variables, but at the end of the day, they're global, not specific to a class.
What the IDE's code sense help suggests is not an absolute indication of what the Objective C language "likes". Putting a global variable either outside or inside a class implementation generates equivalent compiled results from the Xcode Objective C compiler, and with no warnings.
However it might be considered better code style to put these declarations outside of the class implementation, as all global variables actually have global scope, not class scope.
In addition, you might want to assign an initial value to your global variables, instead of making any assumptions in your code of what that might be.
Objective-C does not have class variables. You declare a C global variable. This is how I do it:
Declare a static C variable. In your case:
static BOOL isChecked = NO;
Notice that I have initialized it. Notice that it is declared static, which restricts its visibility to the .m file it is declared in.
If you want a global object such as an array, you will need to initialize it:
static NSArray *myArray;
#implementation MyClass
+ (void)initialize {
if (self == [MyClass class]) {
myArray = [NSArray arrayWithObjects: ... ];
}
}
#end
Notice the if (self == [MyClass class]) check. +initialize will be called one or more times. Once when MyClass is used for the first time (e.g. call to +alloc). Possibly again when any subclass of MyClass is used for the first time. Hence the check to see if you are actually initializing MyClass, or a subclass of it.
And finally, if you want to access this variable outside of the code that it is declared in, create an accessor:
+ (BOOL)isChecked {
return isChecked;
}
Don't expose the global C variable by removing the static modifier. It makes it harder to debug, refactor or re-implement (what if isChecked suddenly depends on other state, how do you change this if it is directly used all over the place in other code?)