6

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. :)

Community
  • 1
  • 1
  • How does Interface Builder come into the picture? How is it related? What's wrong with your current code? –  Nov 21 '12 at 22:32
  • It's used to bind a control in IB. –  Nov 21 '12 at 22:51

3 Answers3

10

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.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 1
    That's correct. But I was talking about ARC. Which doesn't allow that trick `[[Singleton sharedInstance] retain]` –  Nov 21 '12 at 23:07
  • @Eugene so then take out the call to `retain`. – Dave DeLong Nov 22 '12 at 02:43
  • I still don't follow. If I take out `retain` – my singleton will be released when IB would be done with it. –  Nov 22 '12 at 08:38
  • @Eugene, using ARC, objects are automatically retained or released, you do not need to (and actually cannot) do it. The only think that you can do is set the receiving property to be strong or weak. Strong properties will keep (sort of retain) the object. Weak references will be dropped if only weak references to the object remains. – Resh32 Nov 22 '12 at 15:04
  • Well... I still don't fully understand why it works but it works. Please correct me if I'm wrong because I see the next flow: IB object lifecycle: `[[alloc] init]` (in our case it would be `[Singleton instance]`) And for the balance, that object should be released by the environment after usage. So, internally, it would get `objc_release`. Why it's still alive? L) –  Nov 27 '12 at 15:35
  • 3
    My Xcode complained that I cannot assign self outside of a init method. I worked around this by renaming `actualInit` to `- (id)initWithNil: (id) theNil`. Works very well then. – codingFriend1 Feb 07 '13 at 10:28
  • 1
    @codingFriend1 yes, that's a recent development in Clang. You could solve this by changing the name of the `actualInit` method to begin with `init` (as you correctly discovered). – Dave DeLong Feb 07 '13 at 13:11
3

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
  • For everyone's (mostly mine) benefit... I [created an only-slightly modified Gist](https://gist.github.com/mralexgray/6891945) of this. Why? Because it is the ONLY thing that works. – Alex Gray Oct 08 '13 at 21:25
0

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

HoNooD
  • 21
  • 2