views:

799

answers:

6

Hi there!

I’m sure this is a simple one, but it’s been elusive so far, and I’m stumped ...

How do I declare an Ivar so that it’s accessible from ALL Classes in a project?

[Don’t know if it matters, but the ivar in question is (an instance of) my Model class, whose data needs to be accessible to various view controllers.]

Best as I can tell from "The Scope of Instance Variables” in The Objective-C 2.0 Programming Language ... this would be by using the “@public” directive.

So I’ve tried this in the @interface block where the ivar is declared:

@interface ...

...

@public
ModelClass *theModel;

@end

... But when I try to refer to “theModel” in a different class, the compiler doesn’t auto-complete, and when I type it in anyway, the compiler shows: “Error: ‘theModel’ undeclared (first use in this function)”.

I assume this is a question of Scope, and that I haven’t made the ivar available appropriately, but how? Somehow I need to access this, or make its pointer available somehow.

Any ideas would be VERY much appreciated. Many thanks!

+4  A: 

You cannot access iVars from any other class.

You have to declare a getter/setter method to change or view a variable from another class - what you are really looking for are properties, that make it easier to define and access these getter/setter methods.

In your example above, you'd have the property defined just after the block that defines the local variable in the header file:

@property (nonatomic, retain) ModelClass *theModel;

In the implementation file you'd have the getter/setter created with the @synthesize statement just after the @implementation line:

@synthesize theModel;

Then if you have an instance of your class created, you access the class instance variable like so:

myInstance.theModel = [[[ModelClass alloc] init] autorelease];

The reason @public & @private are in there are to define visibility for subclasses (which, being extensions of that class type also get all the class local variables defined by a superclass), not for any random class.

Kendall Helmstetter Gelner
Thanks so much, Kendall, for the info. This makes perfect sense to do ... except I suppose the issue then becomes how to make 'myInstance' visible to other classes? The View Controllers that need access to the data, in this case, are part of an iPhone 'Tab Bar' application template. Currently, I have 'theModel' instantiated in the View Controller for the first tab. But maybe this is a mistake, since I'm not sure how to get at it from the other controller classes ... Would it be better practice to instantiate 'theModel' from the App Delegate instead? And how would I refer to it then? Thanks!
Use a property as I outlined the first half of the message - then you can set it from elsewhere after you make the instance, and also read the variable. Do not forget to release the variable in dealloc.I strongly suggest reading the Apple documentation on properties but you could just use the example I gave and it would work for you.
Kendall Helmstetter Gelner
You could alternatley define theModel in the app delegate, and it may make more sense to store it there - but you would still need a property defined in your app delegate to be able to see it from the view controller in the tab.
Kendall Helmstetter Gelner
Thank you, Kendall. Defining theModel in the app delegate sounds like a good idea! But then -- and apologies for missing something probably rather obvious here -- how do I actually refer to that property from all the different view controllers? "[super theModel]"? Or how?
newacct
A: 

Think a C global variable.

Adding:
extern ModelClass* theModel;
after the @end in the header will make the variable visible anywhere you include the header.

In the ModelClass.cpp file add:
ModelClass* theModel;
before the class implementation.

The variable will still have a value of nil until you allocate and initialize it though and you will be resposible for ensuring that it gets deallocated at the correct time.

CynicismRising
while this may work, it's certainly not the "Objective-C way" of doing it
cobbal
Thanks, CynicismRising, for the info! This does seem to answer my question (though I haven't been able to get it to work yet; I'm not using C++, but Objective-C, if it matters) ... Though I also wonder, even if I could get it to work -- whether this violates OOP encapsulation somehow? Is there a better, preferred way to make data (an instance of ModelClass, in this case) available to all other classes in the project? Thanks!
@cobbal: As I suspected. Then what is?
Yes this approach totally breaks encapsulation. Furthermore this means you could only ever have one theModel, whereas in the original code as a class instance variable you could hold on to multiple models if you desired.
Kendall Helmstetter Gelner
+1  A: 

The standard Objective-C way of doing it is to have a class method that returns the ivar

In your .h file:

+ (id)defaultModel;

and in your .m file:

static ModelClass * defaultModelInstance;

@implementation ModelClass

+ (id)defaultModel {
    if (!defaultModelInstance) {
        defaultModelInstance = [[ModelClass alloc] init];
    }
    return defaultModelInstance;
}

@end

although this will need tweaking if you need a specific ivar instead of just "a ivar that's always the same"

this type of design is used by many Cocoa classes i.e. [NSWorkspace sharedWorkspace]

cobbal
i though "ivar" meant instance variable, whereas you are talking about class variables
newacct
indeed, it looks like I misunderstood the question
cobbal
+1  A: 

Perhaps you forgot to put the instance variable inside the braces of the class where all instance variable declarations go?

@interface Foo : NSObject {

    // other instance variable declarations

    @public
    ModelClass *theModel;
}

// method and property declarations

@end

Also, can you show us the code of how you are trying to access the instance variable from elsewhere? The proper syntax should be:

myFooInstance->theModel

where myFooInstance is a value of type "Foo *"

newacct
Thanks for double-checking about the brackets. Even though I wasn't clear in my code outline (above), yes, they are within the brackets. Though I wasn't aware of the "->" operator, so will have to check it out. Thank you again!
"->" is the C operator for accessing a field of a structure through a pointer to the structure. For objects in Objective-C, "->" is how you access an instance variable of an object through a pointer to the object. This is the same as "->" in C++ and "." in Java. You don't see "->" being used as often in Objective-C because it's discouraged to access fields of other objects; the message passing mechanism is preferred instead. "->" is still used sometimes for accessing a field of your own object, "self->field", when the field name has been hidden by a local variable of the same name.
newacct
But you definitely should be able to use "->" to access a public field of an object (if the variable has the correct type), if you wanted to. I can't imagine how you could have tried to access a field in another object without using the "->" operator.
newacct
+1  A: 

I make properties available to all views managed by a Tab Bar via a singleton representing my data model. This is efficient and allows all Views access to the data (as well as any other application elements. Creating the singleton is straightforward (there are a ton of examples on S.O.). The you just request the instance and get the property values you need.

Here is a framework fro creating the Singleton. The key points are the static instance and the fact that you do the initialization as [[self alloc] init];. This will ensure the object gets cleaned up correctly. All the methods at the bottom of the class are standard from the SDK Docs to make sure release calls are ignored (because the object is shared globally).

Singleton Boilerplate (ApplicationSettings.m):

static ApplicationSettings *sharedApplicationSettings = nil;

+ (ApplicationSettings*) getSharedApplicationSettings
{
    @synchronized(self) {
        if (sharedApplicationSettings == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedApplicationSettings;
}

+ (id)allocWithZone:(NSZone *)zone
{
    @synchronized(self) {
        if (sharedApplicationSettings == nil) {
            sharedApplicationSettings = [super allocWithZone:zone];
            return sharedApplicationSettings;  // assignment and return on first allocation
        }
    }
    return nil; //on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain
{
    return self;
}

- (unsigned)retainCount
{
    return UINT_MAX;  //denotes an object that cannot be released
} 

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self;
}
MystikSpiral
Thanks, MystikSpiral! In fact, I am doing a Tab Bar application, with one data model shared between the view controllers. So the Singleton approach seems like the most sensible in this case. (I've also run across some other interesting ideas, and will post in a separate 'answer'. But because you've mapped things out so thoroughly here, I think your answer is the most appropriate in this case. THANK YOU!
A: 

THANK YOU ALL for the very helpful discussion on this topic! Clearly there are several ways to approach things here, so this is a very useful assortment of techniques.


Just to let y'all know that in researching this issue further, I ran across a couple of other very helpful pages, listed below. They include mention of the NSNotificationCenter, which I hadn't heard of before; as well as the idea of the "dependency injection" design pattern.

The idea is to keep "low coupling"(1) between the classes, making the code more modular & better for unit testing.

And while the 'notification' pattern sounds like a great idea, in this case it may be a bit overkill, considering that I only need ONE instance of the data model throughout the run of the app, and it doesn't change throughout.

Finally, even though the "@public" compiler directive is well-documented in Apple's Obj-C guide(2), I later found a fascinating edict in a different doc stating that it shouldn't be used! Quoted from Apple's own Cocoa Fundamentals(3):
"Give the proper scope to your instance variables. Never scope a variable as @public as this violates the principle of encapsulation. ..."
(Strange that they don't mention this in their 'Objective-C 2.0' guide where the directive is actually explained.)

Anyway, here are a couple of other links I found to be full of some great insights as well. FYI:

  • S.O.: "What’s the best way to communicate between viewcontrollers?"(4) <<

  • CocoaWithLove: "Five approaches to listening, observing and notifying in Cocoa"(5)

  • CocoaWithLove: "Singletons, AppDelegates and top-level data"(6)


Hope these help. Anyway, thank you all again!

Best, rond

P.S. Yikes! It won't let me post more than one inline hyperlink, so I'm listing them here instead. Obviously, they’re all prefixed by “http://” ... :O

(1): en.wikipedia.org/wiki/Coupling_(computer_science)
(2): developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/ObjectiveC/Articles/ocDefiningClasses.html#//apple%5Fref/doc/uid/TP30001163-CH12-TPXREF127
(3): developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW12
(4): stackoverflow.com/questions/569940/whats-the-best-way-to-communicate-between-viewcontrollers
(5): cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html
(6): cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html

rondoagogo