1

This is slightly different from the standard singleton pattern, in that if all external references to an object have released, then the singleton will be released, too. Then, later, when a new object is requested, a new singleton is created. So, something like this:

MyThing *thing1 = [MyThing new];
MyThing *thing2 = [MyThing new];
// thing1 & thing2 are the same object.

thing1 = nil;
thing2 = nil;

thing1 = [MyThing new];
thing2 = [MyThing new];
// thing1 and thing2 are a the same objet, but it's a different object than above.

I tried to use a weak static variable to hang on to my scoped singleton, but that didn't work, since I have no way to increment the retain count under ARC. Which leaves me wondering: is this even possible?

Community
  • 1
  • 1
theory
  • 9,178
  • 10
  • 59
  • 129
  • Why do you need this? – Abizern Oct 31 '14 at 21:18
  • 1
    Seems like a weak reference accessed via factory would do it. The factory would have to work kind of like a singleton factory. – Hot Licks Oct 31 '14 at 21:21
  • Because I won't often need this object, but when I do, I will want to access the same object in a bunch of decoupled places at once. – theory Oct 31 '14 at 21:21
  • 1
    thing1 and thing 2 are not the same object. They are two different instance of the class MyThing. – rdelmar Oct 31 '14 at 21:21
  • @HotLicks That's what I thought, but it's not actually working. Maybe I shouldn't assign `nil` to it in `-dealloc`? If it's weak, does it go to `nil` on its own when the final reference is deallocated? – theory Oct 31 '14 at 21:24
  • In theory the weak reference goes to nil on its own. The trick with the factory is to get a strong reference before testing if the weak reference is nil (and do it "safely" with regard to concurrency) so that you are guaranteed to recreate the object if and only if the global weak reference is nil. – Hot Licks Oct 31 '14 at 21:28
  • @rdelmar If you do nothing special, that's true. I *want* them to be the same, unless both have been released. – theory Oct 31 '14 at 21:29
  • @HotLicks I've been using `@synchronized(self.class)` in `-init` to protect synchronization. Will keep fiddling with it… – theory Oct 31 '14 at 21:33
  • Poor-man's solution: Create "wrapper" objects on demand which, in their `dealloc` methods manage a private reference count. All operations on the "real" object "pass through" the wrappers, when the private reference count goes to zero you nil out the global strong pointer to the "real" object. – Hot Licks Oct 31 '14 at 21:39

2 Answers2

1

override allocWithZone: to manage a single static instance. If nil, makes a new one, if not nil, returns a retained version of it.

implement dealloc and when called nil the single static instance.

I'm not certain if this will work in ARC, you may need to disable arc for that file.

How expensive is it to keep the object around? It's certainly less hassle to follow a standard singleton pattern and just forget about it.

KirkSpaziani
  • 1,962
  • 12
  • 14
  • You'd also need to swizzle `init`, so that it didn't re-init if you got the already-existing object. – Hot Licks Oct 31 '14 at 21:36
  • Yes, this appears to be the correct answer. I was doing it in `-init`, but moved it `+allocWithZone:` and now it's working as expected. Now just have to figure out how to get `-init` to do nothing for an existing object… – theory Oct 31 '14 at 21:52
  • I think I got it, though it's not generalizable (checking to see if an ivar is set in `-init`). Test passes, though after releasing the object, before creating a new one, I have to put in a call to `NSLog()` or I get back an object with the same address. And even with the `NSLog()` I still sometimes get an object at the same address. – theory Oct 31 '14 at 22:04
0

Based on @KirkSpaziani's answer, here's what I've come up with:

static __weak MyTHing *currentThing = nil;

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    __block id thing = nil;
    dispatch_sync(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (currentThing == nil) {
            currentThing = thing = [super allocWithZone:zone];
        } else {
            thing = currentThing;
        }
    });
    return thing;
}

- (void)dealloc {
    dispatch_sync(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        currentThing = nil;
    });
}

This assumes that the initializer can deal with "dirty data", as described by Mike Ash.

theory
  • 9,178
  • 10
  • 59
  • 129
  • Now if only I could prevent Objective-C from using the same memory address after the objet has been released, I could have a nice test to make sure it works. As it is, I can see it works by adding a log of `NSLog()` calls, but I'd be more comfortable with a reliable unit test. – theory Oct 31 '14 at 22:51
  • You should look at http://stackoverflow.com/questions/7274360/how-objective-c-singleton-should-implement-init-method – rmaddy Oct 31 '14 at 23:47
  • Log in dealloc? That should be the most reliable way. Also if multiple threads can create these objects, consider using GCD's dispatch_sync instead of @synchronized(self) - it should be more performant. – KirkSpaziani Nov 01 '14 at 22:23
  • @KirkSpaziani Good call, thanks. Yes, I confirmed it worked by putting log call in `-dealloc`, but what I need is for the allocation to not use the same memory address while unit tests are running. – theory Nov 03 '14 at 22:22