带有参数 iOS 的单例

Singleton with parameter iOS

我需要实现一个接受参数的单例 class。每次都会将同一个对象作为参数传递,因此生成的单例对象将始终相同。

我正在做类似下面代码的事情。这看起来不错吗?有没有更好的方法来实现我想要实现的目标?

  - (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
      if (!sharedInstance) {
        @synchronized(self) {
          sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
        }
      }

      return sharedInstance;
    }

    - (id)initWithAccount:(UserAccount *)userAccount {
      self = [super init];
      if (self) {
        _userAccount = userAccount;
      }

      return self;
    }

    - (id)init {
      NSAssert(false,
               @"You cannot init this class directly. It needs UserAccountDataSource as a paramter");
      return nil;
    }

    + (id)alloc {
      @synchronized(self) {
        NSAssert(sharedInstance == nil, @"Attempted to allocated a second instance of the singleton");
        sharedInstance = [super alloc];
        return sharedInstance;
      }
      return nil;
    }
objA = [Object sharedInstanceWithAccount:A];
objB = [Object sharedInstanceWithAccount:B];

B 被忽略。 objB 中的 userAccount 是 A.

如果 objB 中的 userAccount B,您将更改 sharedInstanceWithAccount。

- (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
    static NSMutableDictionary *instanceByAccount = [[NSMutableDictionary alloc] init];
    id instance = instanceByAccount[userAccount];

    if (!instance) {
        @synchronized(self) {
            instance = [[[self class] alloc] initWithAccount:userAccount];
            instanceByAccount[userAccount] = instance;
        }
    }

    return instance;
}

这个设计中存在一些问题:

  1. 按照 Apple 的建议,对于单例,应该 dispatch_once 而不是 @synchronized(self)

    static MyClass *sharedInstance = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
         sharedInstance = [[MyClass alloc] init];
         // Do any other initialisation stuff here
    });
    return sharedInstance;
    

    更多细节请参考这个问题:Why does Apple recommend to use dispatch_once for implementing the singleton pattern under ARC?

  2. 将单例放入 alloc 的错误 API 设计。

    如方法名称alloc所示,这意味着将分配一些内存。但是,就您而言,事实并非如此。这种覆盖 alloc 的尝试将导致您团队中的其他程序员感到困惑。

  3. 在你的 -init 中使用 NSAssert 是个坏主意。

    如果你想禁用一个方法,把它放在你的头文件中来禁用它:

    - (id)init __attribute__((unavailable)); 
    

    在这种情况下,您会得到一个编译错误,而不是在 运行 时使应用程序崩溃。 有关详细信息,请参阅此 post:

    此外,您甚至可以添加不可用消息:

    - (id)init __attribute__((unavailable("You cannot init this class directly. It needs UserAccountDataSource as a parameter")));
    
  4. 有时会忽略输入参数而不会发出警告。

    在您的以下代码中,如果 class 的实例已由其他人创建,调用此函数的程序员如何知道输入参数 userAccount 有时会被忽略?

    - (id)sharedInstanceWithAccount:(UserAccount *)userAccount {
        if (!sharedInstance) {
            @synchronized(self) {
               sharedInstance = [[[self class] alloc] initWithAccount:userAccount];
            }
        }
        return sharedInstance;
    }
    

总之,不要认为创建带参数的单例是个好主意。使用传统的单例设计要干净得多。