静态字符串数组 - 如何/在哪里在视图控制器中初始化

50
在一个主从应用中,我想显示一个标题为“你的回合”、“他们的回合”、“已赢游戏”、“已输游戏”和“选项”的5个部分的TableView:
因此,我在Xcode 5.0.2中创建了一个空白的主从应用程序,然后在其MasterViewController.m中(它是一个UITableViewController),我尝试实现以下方法:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return _titles[section];
}
我的问题是如何初始化NSArray _titles?在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
{
     ....
}

但是上面两种尝试都给了我语法错误:

enter image description here

更新:

让我惊讶的是,针对这个简单的问题有很多建议,但作为一个iOS/Objective-C新手,我不确定哪种解决方案最合适。

dispatch_once-它不是在多线程应用程序中执行某些操作一次的运行时操作吗?这不会过度杀伤性能吗?我期望找到一个编译时解决方案来初始化常量数组...

viewDidLoad-当我的应用程序在后台和前台之间切换时,它不会无休止地反复初始化我的常量数组吗?

我应该在awakeFromNib中设置NSArray(因为我对所有的ViewControllers都使用stroyboard场景)?或者是在initSomething中(正确的方法是initWithStyle吗?)


viewDidLoad 中初始化它。 - duci9y
你应该将签名更改为 static NSArray* const。如下面的答案所示,有许多解决初始化数组的方法。我喜欢懒加载,但它并不适用于这种情况。 - Hugo Tunius
你所需要的是(哎呀!)单例模式的初始化。它不必在其自己的类中,但它可以成为这个类的一部分。 - Hot Licks
@AlexanderFarber iOS应用程序是多线程的,类方法可以被多个线程同时调用,因此最好使用dispatch_once或某种形式的锁定。我可以亲身经历告诉你,否则崩溃错误的潜在风险非常真实。 - jlehr
9个回答

83

编写一个类方法,该方法返回数组。

+ (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;
}

然后你可以像这样在任何需要的地方访问它:

NSArray *titles = [[self class] titles];

2
用户正在询问在哪里创建,而不是如何创建。您没有提供任何帮助,用户的回答显示他试图在接口中实例化一个对象。 - Simon McLoughlin
1
@SimonMcLoughlin引用原帖中的话,“我的问题是如何初始化NSArray _titles?”[强调添加] - jlehr
1
@jlehr,如果您继续阅读问题的其余部分,您会发现由于上述问题,他正在收到语法警告。用户正在询问如何在viewController对象的流程中实例化一个对象的基本理解。您提供的解决方案可能是创建它的最佳方式,但用户显然正在寻找在哪里创建它的信息。 - Simon McLoughlin
3
对象不能在编译时分配,所以这通常是我们所做的。请注意,dispatch_once是一个Grand Central Dispatch函数,它确保所提供的代码块仅在应用程序生命周期内运行一次。 - jlehr
3
您可以使用完全限定的类名来调用静态方法。与[[self class] titles]相比,您只需调用[MyClassName titles]即可。 - Brody Robertson
显示剩余4条评论

12

你可以在类方法+initialize中初始化它。

static NSArray *_titles_1;

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

谢谢,但我已经搜索过了,似乎(令人惊讶!)当继承的类没有实现自己的*+initialize*方法时,这种方法可以被调用多次。 - Alexander Farber
1
@AlexanderFarber 然后检查_titles_1是否为nil,或者在+initialize中对当前类进行检查。 - Emmanuel
如果我检查 nil - 是否可能发生 +initialize 方法运行一次,但是针对继承类的错误类? - Alexander Farber
+initialize 方法只会在类本身和没有覆盖它的每个子类中运行一次。这就是为什么首先要比较 "if (self == [MyIntendedClass class])"。检查 nil 的原因是因为两个子类可能从不同的线程初始化,所以需要使用同步。 - gnasher729
1
根据苹果文档,@gnasher729 +initialize 是线程安全的:运行时以线程安全的方式向类发送initialize消息。 - Emmanuel

8
你应该在 @implementation 上面声明你的静态数组。
static NSArray *titles_1 = nil;

@implementation ...

initawakeFromNib等方法中定义它,或是在像viewDidLoadapplicationDidFinishLaunching这样的任何其他方法中定义它,具体取决于您的需求。

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

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

7
你还可以像这样做:
static NSString * const strings[] = {
        [0] = @"string_1",
        [1] = @"string_2",
        [2] = @"string_3",
        [3] = @"string_4",
        // ...
    };

这不是一个 NSArray,但你可以通过这个方式访问NSStrings: strings[n]


有时候,最简单的解决方案不仅是最有效的,而且是最好的...由于我对Obj-C相对较新(已经7年了),所以语法让我感到困惑,但我很确定,一个简单的静态数组不需要涉及25行代码..我的C/C++背景告诉我这可能会起作用...NSString *dayNamesArray[7] = {@"blah",@"blah",@"blah",@"blah",@"blah",@"blah",@"and blah"}; 我要赞扬你的常识解决方案...谢谢! - user2052717

6
我想知道以下方法是否是一个好的方式(回答我的问题):
#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"
    ];
}

这样,const NSArray 只在需要它时(在 MasterViewController 类中)被初始化一次。而且,self 检查可以防止此方法再次运行-当某些继承类没有实现自己的 +initialize 方法时。


2

您不能以这种方式实例化对象,只能在接口中声明它们。请按照以下步骤操作:

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
  • 或者你可以使用视图控制器的init方法,而不是viewDidLoad方法

这意味着每个实例都会有自己的数组副本。我不认为这是 OP 寻找的内容。 - jlehr
很好,重点仍然是他试图在接口中实例化对象,这将消除他的Xcode编译器警告,我会用静态数组进行编辑。 - Simon McLoughlin
仔细观察,你会发现 OP 发布了两个不同的失败尝试,第一个尝试是使用 NSArray 初始化全局常量。 - jlehr
@SimonMcLoughlin - 你能说“过早优化”吗? - Hot Licks
如果我使用viewDidLoad,那么当我按Home键将应用程序发送到后台,然后再将其带回前台时,*_titles*会一遍又一遍地被重新分配吗? - Alexander Farber
显示剩余2条评论

2

dispatch_once 可以正常运作,它可以在复杂的情况下工作,也可以在简单的情况下工作。为了保持一致性,在所有情况下都使用它是最好的选择。例如,当您将所有常量字符串替换为 NSLocalizedString() 的调用时,这将使得代码更加容易。无需更改代码。

+ (void)initialize 中做事情并没有错,但我遇到过这样的情况:我想先配置一个类然后再真正使用它,而 initialize 当然会在任何可能的配置方法开始执行之前调用。而且有些情况下您可能没有类上下文。使用 dispatch_once 总是可行的。


0
为什么不使用:< /p>
+(NSString*)titleForSection:(NSInteger)section {
    return (@[ @"title1", @"title2", @"title3" ])[section];
}

-1

我更喜欢使用Objective-C ++,将文件名从xxx.m改为xxx.mm,并且NSArray的初始化会在运行时发生

没有 dispatch_once没有类方法,只需一行写入:

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

使用该代码会出现以下错误:"初始化元素不是编译时常量"。 - Tiago Mendes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接