2

I'm having trouble creating a nice way of passing a collection around to different view controllers. For example, I created a custom class called Message with a bunch of attributes. I want to have a global NSMutableArray of those stored in a global variable of sorts called messages that I can add to or get from anywhere. Everyone on Stackoverflow says not to use your delagate class to store global variables so I created a singleton class called Shared. In there I created a property for the NSMutableArray called messages like this:


@interface Shared : NSObject {

}

@property (nonatomic, retain) NSMutableArray *messages;

+(Shared *) sharedInstance;

@end

And my .h file is (the important part):


#import "Shared.h"
static Shared* sharedInstance;

@implementation Shared

@synthesize messages;

static Shared *sharedInstance = nil;

-(id) init {
    self = [super init];
    if (self != nil){

    }
    return self;
}

-(void) initializeSharedInstance {

}

+ (Shared *) sharedInstance{
    @synchronized(self) {
        if (sharedInstance == nil){
            sharedInstance = [[self alloc] init];
            [sharedInstance initializeSharedInstance];

        }
        return (sharedInstance);
    }
}

In my other view controller, I first import "Shared.h", then try this:


[[Shared sharedInstance].messages addObject:m];

NSLog([NSString stringWithFormat:@"Shared messages = %@", [Shared sharedInstance].messages]);

It keeps printing null instead of the the collection of m objects. Any thoughts?

nash
  • 2,181
  • 15
  • 16
Jody G
  • 583
  • 8
  • 21
  • and where do you initialise messages? I think @synthesize doesn't initialise the variable. – Matt N. Apr 26 '11 at 20:28
  • Perhaps people say not to use the app delegate to store global variables because they don't recommend global variables? User defaults or passing the data along as view controllers get created also works. – Terry Wilcox Apr 26 '11 at 20:30
  • Matt, I just added the init so you can see that part. Terry, good point. If I can't solve the problem this way, I'll probably end up taking that route. For sanity I'd like to solve this one if possible though. – Jody G Apr 26 '11 at 20:50
  • Matt, I think I misunderstood your question. I showed my init function but I probably have to actually call init from my other Class where I first use it. – Jody G Apr 26 '11 at 23:13

3 Answers3

3

You need to have a static variable.

In .h:

@interface Shared : NSObject
{
    NSMutableArray *messages;
}

@property (nonatomic, retain) NSMutableArray *messages;

+ (Shared*)sharedInstance;

@end

in .m:

static Shared* sharedInstance;

@implementation Shared

@synthesize messages;


+ (Shared*)sharedInstance
{
    if ( !sharedInstance)
    {
        sharedInstance = [[Shared alloc] init];

    }
    return sharedInstance;
}

- (id)init
{
    self = [super init];
    if ( self )
    {
        messages = [[NSMutableArray alloc] init];
    }
    return self;
}
Haris
  • 13,645
  • 12
  • 90
  • 121
Joseph Lin
  • 3,324
  • 1
  • 29
  • 39
  • 1
    It's really not a big crime to store global variables at your app delegate class, especially in your case since it's just an array. – Joseph Lin Apr 26 '11 at 20:31
  • Joseph, I just added the static variable as suggested. Still seems to be giving null though. – Jody G Apr 26 '11 at 20:52
  • How would I init my array from the other class? I tried [[Shared sharedInstance].messages init]; but that didn't seem to do it. I'm probably just down to an init syntax thing when dealing with a static variable from another class. – Jody G Apr 26 '11 at 23:07
  • 1
    Jody, don't init from another class. That class won't know whether messages is already in use or not. Instead, the init method of your Shared class should alloc/init your array (exactly like in Joseph's answer). That way, as soon as an instance of Shared is created, it will get an initialized array, usable from any other class. – nash Apr 27 '11 at 06:33
1

A thought:

@synthesize generates setter and getter methods, it doesn't init your variable. Where do you do that? I can't see it in the excerpts you posted.

Matt N.
  • 1,239
  • 2
  • 11
  • 26
  • I think maybe that's it. I'm not sure though how to init a variable from another class. I tried [[Shared sharedInstance].messages init]; – Jody G Apr 26 '11 at 23:11
  • *+sharedInstance* is a class method that returns an instance of the class. It creates the instance by calling the *-init* method if no instance exists yet (it checks the static variable for an existing instance). Inside the *-init* method you should initialize your array as per example of Joseph Lin. If the array isn't initialized yet, there is a chance it's nil. Since one can send messages to nil in objective-c this won't return an error, though the array won't behave as one would expect - you wouldn't be able to store values in it, for example. – Wolfgang Schreurs Apr 27 '11 at 00:15
  • P.S.: in your current implementation the method *-initializeSharedInstance* serves no use and I'd consider removing it. – Wolfgang Schreurs Apr 27 '11 at 00:17
  • Wolfgang, you were right. I had overlooked the init part of Joseph Lin's example. I wish I could give you both credit! – Jody G Apr 27 '11 at 01:04
1

The following is not an answer to your issue, but instead a suggestion to an alternative approach that (in my opinion) is 'cleaner' in use.

An alternative to using a Singleton to store app-wide could be to define a class with class methods that retrieves values from the NSUserDefaults. This class could be imported into the prefix header (*.pch) so you can access it from every other class in the project.

Methods inside this class could look like this:

inside Settings.h:

// for this example I'll use the prefix for a fictional company called SimpleSoft (SS)
extern NSString *kSSUserLoginNameKey;

+ (NSString *)userLoginName;
+ (void)setUserLoginName:(NSString *)userLoginName; 

inside Settings.m:

kSSUserLoginNameKey = @"SSUserLoginNameKey";

+ (NSString *)userLoginName 
{
    return [[NSUserDefaults standardUserDefaults] valueForKey:kSSUserLoginNameKey];
}

+ (void)setUserLoginName:(NSString *)userLoginName 
{
    [[NSUserDefaults standardUserDefaults] setValue:userLoginName forKey:kSSUserLoginNameKey];
    [[NSUserDefaults standardUserDefaults] synthesize];
}

Of course in a setup like this NSUserDefaults is the singleton that is being accessed through a convenience class. This class acts as a wrapper around the NSUserDefaults singleton. Values can be accessed like this:

NSString userLoginName = [Settings userLoginName];
[Settings setUserLoginName:@"Bob"];

Other objects -like Arrays- could be accessed in much the same way. One thing to be careful with (much the same as with your current approach) is to be careful not to access a class like this from every other class. Components that are intended to be reusable should pass values, so the components of the application don't become too tightly coupled to the settings class.

Wolfgang Schreurs
  • 11,779
  • 7
  • 51
  • 92
  • This would be a lot of work though because I'm storying an array of custom objects. Serializing that could be a lot of work. – Jody G Apr 26 '11 at 23:12
  • Depends, you could store the array in NSUserDefaults (using the *+objectForKey:* and *+setObject:forKey:* methods) since it's a descendant of NSObject - you don't have to store each separate value from the array in a distinct variable, you could just store the whole array. – Wolfgang Schreurs Apr 26 '11 at 23:16
  • Perhaps you should also check out this post, just in case your array contains custom objects: http://stackoverflow.com/questions/4197246/storing-nsmutablearray-filled-with-costum-objects-in-nsuserdefaults-crash/4197344#4197344 – Wolfgang Schreurs Apr 26 '11 at 23:22