首页 > 解决方案 > Objective-C 块的全局默认值

问题描述

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

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. 在单独的文件中定义默认的常量块并从我的实现类中引用的好方法是什么?

标签: objective-c

解决方案


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

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

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

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

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

//  Foo.m

#import "Foo.h"

NSInteger baz = 42;

@implementation Foo

@end

...我们在其.h标题中公开该全局:

//  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. 为什么有一个初始化器是个问题?

有效的extern意思是“在其他地方有一个全球实施”。在那里初始化它没有意义。

  1. 在单独的文件中定义默认的常量块并从我的实现类中引用的好方法是什么?

虽然您可以使用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 方法,我们可能只创建一个类方法:

// 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

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


推荐阅读