51

In a Master-Detail app I'd like to display a TableView with 5 sections titled:

  1. Your Move
  2. Their Move
  3. Won Games
  4. Lost Games
  5. Options

So I create a blank Master-Detail app in Xcode 5.0.2 and then in its MasterViewController.m (which is a UITableViewController) I'm trying to implement the method:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return _titles[section];
}

My question is however how to init the NSArray _titles?

I'm trying in the MasterViewController.m:

#import "MasterViewController.h"
#import "DetailViewController.h"

static NSArray *_titles_1 = @[
    @"Your Move",
    @"Their Move",
    @"Won Games",
    @"Lost Games",
    @"Options"
];

@interface MasterViewController () {
    NSMutableArray *_games;

    NSArray *_titles_2 = @[
                         @"Your Move",
                         @"Their Move",
                         @"Won Games",
                         @"Lost Games",
                         @"Options"
    ];
}
@end

@implementation MasterViewController

- (void)awakeFromNib
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        self.clearsSelectionOnViewWillAppear = NO;
        self.preferredContentSize = CGSizeMake(320.0, 600.0);
    }
    [super awakeFromNib];
}

- (void)viewDidLoad
{
     ....
}

but both tries above give me syntax errors:

enter image description here

UPDATE:

To my surprise there are many suggestions for this simple question, but as an iOS/Objective-C newbie I'm not sure, which solution is most appropriate.

dispatch_once - isn't it a runtime operation to execute something once in a multi-threaded app? Isn't it overkill here? I was expecting a compile-time solution for initiating a const array...

viewDidLoad - when my app changes between background and foreground, wouldn't it unnecessary initiate my const array again and again?

Shouldn't I better set the NSArray in awakeFromNib (since I use stroyboard scenes for all my ViewControllers)? Or maybe in initSomething (is the correct method initWithStyle?)

4
  • Init it in viewDidLoad. Commented Dec 12, 2013 at 13:19
  • You should change the signature to static NSArray* const instead. There are many ways to solve when to initialize the array as seen in the answer below. I like lazy inits, but it isn't really suited for this use case. Commented Dec 12, 2013 at 13:26
  • What you're looking for is a (choke!) singleton-style initialization. It does not need to be in its own class, however, it can be a part of this class. Commented Dec 12, 2013 at 13:39
  • @AlexanderFarber iOS apps are multi-threaded, and class methods can be called concurrently by multiple threads, so it's safer to use dispatch_once or some form of locking. I can tell you from first-hand experience that otherwise the potential for crasher bugs is very real. Commented Dec 12, 2013 at 22:08

9 Answers 9

83

Write a class method that returns the array.

+ (NSArray *)titles
{
    static NSArray *_titles;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _titles = @[@"Your Move",
                    @"Their Move",
                    @"Won Games",
                    @"Lost Games",
                    @"Options"];
    });
    return _titles;
}

Then you can access it wherever needed like so:

NSArray *titles = [[self class] titles];
Sign up to request clarification or add additional context in comments.

9 Comments

The user is asking WHERE to create this, not how. You haven't provided any help, the users answer shows him trying to instantiate an object in an interface
@SimonMcLoughlin Quoting the OP, "My question is however how to init the NSArray _titles?" [emphasis added]
@jlehr and if you read the rest of the question you will see he is getting syntax warnings due to the above mentioned issue. The user is asking for a basic understanding of how to instantiate an object in the flow of a viewController object. Fair point your solution might be the best way to create it, but the user is quite clearly looking for info as to WHERE to create it
Objects can't be allocated at compile time, so this is typically what we do. Note that dispatch_once is a Grand Central Dispatch function that ensures the code in the provided block is only run once in the lifetime of the app.
You can use the fully qualified class name to call the static method. Instead of [[self class] titles] you can just call [MyClassName titles];
|
12

You can init it in class method +initialize

static NSArray *_titles_1;

@implementation MasterViewController
+ (void)initialize {
    _titles_1 = @[
        @"Your Move",
        @"Their Move",
        @"Won Games",
        @"Lost Games",
        @"Options"
    ];
}
@end

5 Comments

Thanks, but I've search and it seems (surprisingly!) that this method can be called more than once for a class - when an inheriting class does not implement own +initialize method.
@AlexanderFarber Then check that _titles_1 is nil or for the current class in +initialize
If I check for nil - can't it happen that the +initialize method runs once, but for a wrong class - for the inheriting class?
+initialize runs once for the class itself, and once for every subclass that doesn't override it. That's why you compare "if (self == [MyIntendedClass class])" first. The check for nil makes it necessary to use synchronisation because two subclasses could be initialised from different threads.
@gnasher729 +initialize is thread safe, according Apple documentation : The runtime sends the initialize message to classes in a thread-safe manner.
8

You should declare your static array above @implementation.

static NSArray *titles_1 = nil;

@implementation ...

And define it in init or awakeFromNib or any other method like viewDidLoad, applicationDidFinishLaunching wherever you want.

- (void)initMethod{ //change the method name accordingly

    if (titles_1 == nil){
        [self setTitles_1:@[ @"Your Move", @"Their Move", @"Won Games", @"Lost Games", @"Options" ]];
    }
}

Comments

7

You can also do something like this:

static NSString * const strings[] = {
        [0] = @"string_1",
        [1] = @"string_2",
        [2] = @"string_3",
        [3] = @"string_4",
        // ...
    };

It's not an NSArray but you can access NSStrings like this strings[n]

1 Comment

Sometimes, most always the simplest solution is not only the most effective, but the best... Being relatively new to Obj-C (7 years now) I'm still put off by the syntax, but I was pretty sure, a simple static array didn't need to involve 25 lines of code.. My C/C++ background told me this would probably work... NSString *dayNamesArray[7] = {@"blah",@"blah",@"blah",@"blah",@"blah",@"blah",@"and blah"}; I give you credit for the most common sense solution... Thanks!
6

I wonder, if the following would be a good way (answering my own question):

#import "MasterViewController.h"
#import "DetailViewController.h"

static const NSArray *_titles;

@interface MasterViewController () {
    NSMutableArray *_objects;
    NSMutableArray *_yourMove;
    NSMutableArray *_theirMove;
    NSMutableArray *_wonGames;
    NSMutableArray *_lostGames;
    NSMutableArray *_options;
}
@end

@implementation MasterViewController

+ (void)initialize
{
    // do not run for derived classes
    if (self != [MasterViewController class])
        return;

    _titles = @[
        @"Your Move",
        @"Their Move",
        @"Won Games",
        @"Lost Games",
        @"Options"
    ];
}

This way the const NSArray is initalized once and right before I need it (in the MasterViewController class). And the self check prevents this method from running again - when some inheriting class does not implement its own +initialize method.

Comments

2

You can't instantiate objects in this way, you can only declare them in interfaces. Do the following:

static NSArray *_titles_2;

@interface MasterViewController () {
    NSMutableArray *_games;
}
@end

@implementation MasterViewController
-(void)viewDidLoad()
{
     _titles_2 = @[
                         @"Your Move",
                         @"Their Move",
                         @"Won Games",
                         @"Lost Games",
                         @"Options"
    ];
}
@end
  • or likewise you could use the viewcontroller init method instead of viewDidLoad

7 Comments

That would mean every instance would have its own copy of the array. I don't think that's what the OP was looking for.
fair enough, the point still stands that he's trying to instantiate the object in the interface, this would remove his xcode complier warnings, ill edit with a static array
If you look carefully, you'll see that the OP posted two different failed attempts, the first of which was an attempt to initialize a global constant with an NSArray.
@jlehr - How many instances of this view controller do you think there are going to be?
@HotLicks no that was a valid point, the user did clearly define a static array. The user more than likely had a reason for going out of there way to do that
|
2

dispatch_once works. It works in complicated cases, and it works in simple cases. So to be consistent, the best thing is to use it in all cases. That makes it for example a lot easier when you replace all your constant strings with calls to NSLocalizedString (). No code change needed.

Doing things in + (void)initialize isn't really wrong, but I have had situations where I first wanted to configure a class before really using it, and initialize is of course called just before any possible configuration method would start executing. And there are situations where you don't really have a class context. dispatch_once will always work.

Comments

0

Why don't use :

+(NSString*)titleForSection:(NSInteger)section {
    return (@[ @"title1", @"title2", @"title3" ])[section];
}

Comments

-1

I prefer to use Objective-C++,change filename from xxx.m to xxx.mm,and the initialize of NSArray will happen at runtime

no dispatch_once,no class method,just write it in one line:

static NSArray *const a = @[@"a",@"b",@"c"];

1 Comment

with that code you get the error: "Initializer element is not a compile-time constant"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.