Objective-C 块的全局默认值

Global default value for Objective-C block

我有一个需要一个块的初始化程序。我还想要一个不需要块的便利初始化器,而是使用块的全局默认实例:

ABCDef.m:

#import "ABCDefaultReader.h"

@implementation ABCDef {
  NSString *(^_reader)(NSString *name);
}

- (instancetype)initWithReader:(NSString *(^)(NSString *name))reader {
  self = [super init];
  if (self) {
    _reader = reader;
  }
  return self;
}

- (instancetype)init {
  // ABCDefaultReader imported from ABCDefaultReader.h
  return [self initWithReader:ABCDefaultReader];
}

ABCDefaultReader.h:

extern NSString *(^ABCDefaultReader)(NSString *name);

ABCDefaultReader.m:

#import "ABCDefaultReader.h"

extern NSString *(^ABCDefaultReader)(NSString *name) = ^NSString *(NSString *name) {
  // ...
}

构建失败,因为 'extern' variable has an initializer(我正在使用 -Werror):

ABCDefaultReader.m:3:36: error: 'extern' variable has an initializer [-Werror,-Wextern-initializer]
extern NSString *(^ABCDefaultReader)(NSString *name) = ^NSString *(NSString *name) {
                                                        ^
1 error generated.
  1. 为什么初始化器会出现问题?
  2. 在单独的文件中定义默认常量块并从我的实现中引用的好方法是什么 class?

删除.m 文件中的extern。 “extern”的意思是“这是在别处定义的”,但你在这里定义它。

在 Objective-C 中(或者更准确地说,在 C 中),当你有这样的全局时,

  • 实际的全局变量应该在源文件中(.m Objective-C 文件,或者在 C 的情况下是 .c)。在这里你可以包括初始化。但是不应使用 extern 限定符。

  • .h 应该有 extern 声明(没有初始化)以将此全局公开给其他编译单元。

让我们考虑一个更简单的例子(将 Objective-C 块的句法噪声排除在等式之外)。

因此,我们在 .m 文件中声明了一个全局 ...

//  Foo.m

#import "Foo.h"

NSInteger baz = 42;

@implementation Foo

@end

... 我们在其 .h header:

中公开该全局
//  Foo.h

#import <Foundation/Foundation.h>

extern NSInteger baz;

@interface Foo : NSObject

@end

现在我们可以从其他文件访问该全局文件:

//  Bar.m

#import "Bar.h"
#import "Foo.h"

@implementation Bar

- (void)qux {
    NSLog(@"%ld", (long)baz); // 42
}

@end

你问过:

  1. Why is it a problem to have an initializer?

extern 实际上意味着“在其他地方实现了全局”。在那里初始化它没有意义。

  1. What's a good way to have a default, constant block defined in a separate file, and referenced from my implementation class?

虽然您可以使用上面概述的正确 extern 模式,但我建议您根本不要使用全局变量。对于这个块,我可能有一个 class 属性。或者我可能有一个实例 属性 这个块,然后把它放在一些共享实例中(一个共享实例,你在需要的地方注入,或者在非常狭窄的情况下,可能是一个单例)。在没有更多上下文的情况下很难说出您的特定情况。

但我们通常会避免污染全局命名空间。


顺便说一句,您将此块描述为“常量”。如果您的意思不是“常量”,请忽略以下内容,但如果是,请继续阅读:

正如您定义的这个全局变量,它是可变的,可以被代码中的任何地方替换。如果它真的是一个常量,你应该包括 const 限定符:

// .h

extern NSString *(^const ABCDefaultReader)(NSString *);

// .m

extern NSString *(^const ABCDefaultReader)(NSString *) = ^(NSString *string) {
    // do something with `string`

    NSString *result = ...
    return result;
}

当然,这引出了一个问题,即如果块是常量,为什么还要使用块。块的价值在于调用者可以提供一个代码块,被调用的例程将利用该代码块。例如,如果我们想调用一些 Objective-C 方法,我们可能只创建一个 class 方法:

// Foo.h

@interface Foo: NSObject

+ (NSString *)bar:(NSString *)input;

@end

// Foo.m

@implementation Foo

+ (NSString *)bar:(NSString *)input {
    NSString *output = ...
    return output;
}

@end

然后你会这样称呼它:

// Baz.m

@implementation Baz

- (void)someRoutine {
    NSString *result = [Foo bar:@"baz"];
    // do something with `result`
}

@end

这是一个可以在任何地方调用的例程,无需使用全局变量,不需要实例化有问题的 object,但该方法的命名空间很好。