0

In the answers to a previous question, I learned how to effectively create what I would describe as a cached singleton object: There is only one present at any one time, but if it's not needed, it's deallocated.

In order to test it, I wrote this unit test:

- (void)testThingInstance {
    MyThing *thing1 = [MyThing new];
    MyThing *thing2 = [MyThing new];
    XCTAssertEqual(thing1, thing2, @"Should have single thing");

    // Let's release the thing, but keep its address.
    uintptr_t pointer_as_integer = (uintptr_t)thing1;
    thing1 = thing2 = nil;

    // Now get a new thing. It should be a brand new object.
    thing1 = [MyThing new];
    XCTAssertNotEqual(
        (uintptr_t)thing1, pointer_as_integer,
        @"Should have a new thing, not a permanent singleton"
    );
}

The trouble is, it fails that last assertion fails half the time. I put NSLog() calls in various places in my code to make sure that a new object was, in fact, allocated after the other two references were released. The only thing I can guess is that the compiler is noticing that a memory space just the right size was recently freed and so decides to make use of it. Even when I stick in code to try to allocate something else in between, it still often uses the same memory address.

Is there any way I can get it not to do that? Or, preferably, might there be a better way to make sure that a new object is allocated other than comparing memory addresses?

Community
  • 1
  • 1
theory
  • 9,178
  • 10
  • 59
  • 129
  • 1
    Without seeing how you implemented `MyThing`, no one can help you fix the problem. – rmaddy Nov 03 '14 at 23:41
  • @rmaddy Please see referenced [previous question](http://stackoverflow.com/q/26683532/79202), especially [this answer](http://stackoverflow.com/a/26684525/79202) for the implementation of `MyThing`. – theory Nov 03 '14 at 23:45
  • OK, why do you care whether the next instance uses the same address or not? It shouldn't matter. And are you sure that the `dealloc` is called in the code of this question between setting `thing1` and `thing2` to `nil` and reassigning `thing1` again? – rmaddy Nov 03 '14 at 23:48
  • @rmaddy So that I can *test* it. And yes. I just chased down a bunch of places where it was retained in my tests and needed to be released. – theory Nov 03 '14 at 23:53
  • But it's not a valid test. If an object is deallocated and then a new object is created, it's perfectly valid for the new instance to be given the same memory address of the previously deallocated instance. It's still a new instance. – rmaddy Nov 03 '14 at 23:58

2 Answers2

0

I operate with a slightly different definition of a singleton: its not just a single object at a time, but a single object for the entire execution of the app. I don't ever expect to deallocate it.

But the only way I can think to achieve what you're going for is to have the singleton fake its own death on dealloc, then make things right on the next allocation.

static MyThing *_zombieInstance;

- (void)dealloc {
     _zombieInstance = self;
}

... then, upon creation of a new one:

+ (MyThing *)newMyThing {
    MyThing *thing = [MyThing new];  // assuming you implement single-ness somehow here
    _zombieInstance = nil;
    return thing;
}
danh
  • 62,181
  • 10
  • 95
  • 136
  • Yeah, I have a permanent singleton in a different class in my code. And yes, I could put some (debug-only) code in to catch the old singleton, but really would rather not if I can find some other way to distinguish objects from each other. – theory Nov 03 '14 at 23:52
  • I get it, but if you think about what you're saying: "distinguish objects from each other" ... the two objects you're considering are not two objects. They are **just one object**, identical to itself in every sense - same pointer pointing to the same chunk of memory containing the same bits. They are not distinguishable by definition because they are the same thing. I see how it ain't pretty, but I'm pretty sure that some form of retaining the old instance is the only solution. – danh Nov 04 '14 at 00:25
  • Yeah, that makes sense. I think the best thing I can do is what I've done in the answer below: Inject a property only in the test and check for its presence or absence. Works great and reliably tests exactly what I needed to test. – theory Nov 04 '14 at 01:11
0

Okay, I've come up with a way to do it without modifying the source for the caching class by adding a category that injects a property. In MyThingTests.m, I add this to the top:

#import <objc/runtime.h>

@interface MyThing (ioThingTest)
@property (nonatomic, strong) NSObject *sentinel;
@end

@implementation MyThing (ioThingTest)

- (NSObject *)sentinel {
    return objc_getAssociatedObject(self, @selector(sentinel));
}

- (void)setSentinel:(NSObject *)value {
    objc_setAssociatedObject(
        self, @selector(sentinel), value,
        OBJC_ASSOCIATION_RETAIN_NONATOMIC
    );
}

@end

That injects a property named sentinel. It's only available in the test, of course. Using that, the test becomes:

@implementation MyThingTests

- (void)testThingInstance {
    MyThing *thing1 = [MyThing new];
    thing1.sentinel = NSNull.null;
    MyThing *thing2 = [MyThing new];
    XCTAssertEqual(thing2.sentinel, thing1.sentinel, @"Should have single thing");

    // Let's force a release.
    thing1 = thing2 = nil;

    // Now get a new thing. It should be a brand new object with no sentinel.
    thing1 = [MyThing new];
    XCTAssertNil(thing1.sentinel, @"Should have a new thing with no sentenel");
}

@end

So I'm able to tell that a new object is created by the presence or absence of the sentinel property.

theory
  • 9,178
  • 10
  • 59
  • 129