Singleton in Interface Builder with ARC

2019-03-29 09:11发布

问题:

My question is quite similar to this one: Use Singleton In Interface Builder?

The only difference is that I use ARC. So, if simplified, my singleton looks like that:

Manager.m

@implementation Manager

+ (instancetype)sharedManager {
    __strong static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

@end

So the question is if it's possible to adopt it for Interface Builder still being with ARC?

Of course, I understand that it might be simpler just to rewrite that class without ARC so the question is rather academic. :)

回答1:

When the nib is unarchived, it'll attempt to either alloc/init or alloc/initWithCoder: a new instance of the class.

So, what you could do is intercept that call and re-route it to return your singleton:

+ (id)sharedInstance {
  static Singleton *sharedInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self actualAlloc] actualInit];
  });
  return sharedInstance;
}

+ (id)actualAlloc {
  return [super alloc];
}

+ (id)alloc {
  return [Singleton sharedInstance];
}

- (id)actualInit {
  self = [super init];
  if (self) {
    // singleton setup...
  }
  return self;
}

- (id)init {
  return self;
}

- (id)initWithCoder:(NSCoder *)decoder {
  return self;
}

This allows -init and -initWithCoder: to be safely called multiple times on the same object. It's generally not recommended to allow this, but given that singletons are already cases of "a place where things can get really wonky", this isn't the worst you could do.



回答2:

Just to be complete, here's an implementation of Singleton which might be used from Interface Builder. The difference is in actualAlloc method. As [super alloc] would still call [self allocWithZone:] – it wouldn't allocate the object.

Singleton.h

@interface Singleton : NSObject

+ (instancetype)sharedInstance;

@end

Singleton.m

@implementation Singleton

+ (instancetype)sharedInstance {
    __strong static id _sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self _alloc] _init];
    });
    return _sharedInstance;
}

+ (id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}

+ (id)alloc {
    return [self sharedInstance];
}

- (id)init {
    return self;
}

+ (id)_alloc {
    return [super allocWithZone:NULL]; //this is important, because otherwise the object wouldn't be allocated
}

- (id)_init {
    return [super init];
}

@end


回答3:

@Eugene, from iOS doc set, "For historical reasons, alloc invokes allocWithZone:.", so, there is no need to reimplement the alloc method.