non-ARC


由于Apple曾经给出过一段“标准”实现,稍加修改便可转换为线程安全的代码,不再讨论。

#import "MyManager.h"

static MyManager *sharedMyManager = nil;

@implementation MyManager

@synthesize someProperty;

#pragma mark Singleton Methods
+ (id)sharedManager {
  @synchronized(self) {
      if(sharedMyManager == nil)
          sharedMyManager = [[super allocWithZone:NULL] init];
  }
  return sharedMyManager;
}
+ (id)allocWithZone:(NSZone *)zone {
  return [[self sharedManager] retain];
}
- (id)copyWithZone:(NSZone *)zone {
  return self;
}
- (id)retain {
  return self;
}
- (unsigned)retainCount {
  return UINT_MAX; //denotes an object that cannot be released
}
- (oneway void)release {
  // never release
}
- (id)autorelease {
  return self;
}
- (id)init {
  if (self = [super init]) {
      someProperty = [[NSString alloc] initWithString:@"Default Property Value"];
  }
  return self;
}
- (void)dealloc {
  // Should never be called, but just here for clarity really.
  [someProperty release];
  [super dealloc];
}

@end

code by Matt Galloway

值得注意的一点是alloc消息会随后走allocWithZone:, 就像copy会走copyWithZone:一样,这种方法实现的单例是可以保证使用安全的,即可以保证[MyManager alloc] init][manager copy][MyManager sharedManager]得到同一个实例。


ARC


尽管查了很多资料,但是ARC版本似乎没有类似的保证使用安全的方法。

+ (id)sharedManager {
    static MyManager *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}

- (id)init {
  if (self = [super init]) {
      someProperty = [[NSString alloc] initWithString:@"Default Property Value"];
  }
  return self;
}

在这种实现方式下,如果想要得到使用安全方面的保证(或者部分保证),有下述方式可以尝试:

1.告知编译器不允许调用init方法:

// MyManager.h
- (instancetype)init __attribute__((unavailable("Cannot use init for this class, use +(ArcSingleton*)sharedInstance instead!")));

2.如果还觉得不够,想要在运行时也做到调用禁止,可以这样:


@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
		if (!useinside) {
				@throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
		}
		else {
				return [super alloc];
		}
}

+(id)sharedInstance
{
		static dispatch_once_t p = 0;
		dispatch_once(&p, ^{
				useinside = YES;
				_sharedObject = [[MyClass alloc] init];
				useinside = NO;
		});   
		// returns the same object each time
		return _sharedObject;
}

stackoverflow

有两个地方值得注意。

  1. 如果像非ARC那样加上allocWithZone:并使用 [MyManager alloc] init]初始化,会在dispatch_once那里崩掉。
  2. dispatch_once里面的初始化,无论是向self[self class]MyManageralloc消息应该都是一样的。三者都是MyManager
  3. NSObject协议本身不支持copy,所以copy方法不是必须的。(如果没有,调用(当然)会崩溃)

最近在sunnyxx大神的博客中看到一个脑洞很大的方案。利用 __attribute__((objc_runtime_name(...)))__attribute__((constructor))在运行时动态替换+ alloc- init 等方法直接返回 + sharedInstance 单例。遗憾的是,大神只提供了思路。等啥时候有空了实现下看看。