【问题标题】:How can I distinguish Objective-C objects without comparing memory addresses?如何在不比较内存地址的情况下区分 Objective-C 对象?
【发布时间】:2014-12-30 18:06:25
【问题描述】:

在对previous question 的回答中,我学会了如何有效地创建我将描述为缓存的单例对象:任何时候只有一个存在,但如果不需要它,它已被释放。

为了测试,我写了这个单元测试:

- (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"
    );
}

问题是,最后一个断言失败了一半的时间。我在代码中的不同位置放置了NSLog() 调用,以确保实际上在释放其他两个引用之后分配了一个新对象。我唯一能猜到的是编译器注意到最近释放了一个大小合适的内存空间,因此决定使用它。即使我坚持代码尝试在两者之间分配其他东西,它仍然经常使用相同的内存地址。

有什么办法可以让它不这样做吗?或者,除了比较内存地址之外,是否有更好的方法来确保分配新对象?

【问题讨论】:

  • 没有看到你如何实现MyThing,没有人可以帮你解决问题。
  • @rmaddy 请参阅引用的previous question,尤其是this answer 以了解MyThing 的实现。
  • 好的,你为什么关心下一个实例是否使用相同的地址?应该没关系。您确定在将thing1thing2 设置为nil 并再次重新分配thing1 之间,此问题的代码中调用了dealloc
  • @rmaddy 这样我就可以测试了。是的。我只是追查了一堆在我的测试中保留并需要发布的地方。
  • 但这不是一个有效的测试。如果一个对象被释放,然后创建了一个新对象,那么为新实例提供与先前释放的实例相同的内存地址是完全有效的。它仍然是一个新实例。

标签: objective-c memory memory-management singleton memory-address


【解决方案1】:

我对单例的定义略有不同:它不只是一次单个对象,而是用于整个应用程序执行的单个对象。我从没想过要释放它。

但我能想到的唯一方法是让单例在 dealloc 上假装自己的死亡,然后在下一次分配时把事情做好。

static MyThing *_zombieInstance;

- (void)dealloc {
     _zombieInstance = self;
}

...然后,创建一个新的:

+ (MyThing *)newMyThing {
    MyThing *thing = [MyThing new];  // assuming you implement single-ness somehow here
    _zombieInstance = nil;
    return thing;
}

【讨论】:

  • 是的,我的代码中有一个不同类的永久单例。是的,我可以放入一些(仅调试)代码来捕捉旧的单例,但如果我能找到其他方法来区分对象,我真的宁愿不这样做。
  • 我明白了,但是如果您考虑一下您所说的:“将对象彼此区分开来”……您正在考虑的两个对象不是两个对象。它们只是一个对象,在任何意义上都与自身相同——相同的指针指向包含相同位的相同内存块。它们在定义上是不可区分的,因为它们是同一事物。我知道它不漂亮,但我很确定保留旧实例的某种形式是唯一的解决方案。
  • 是的,这是有道理的。我认为我能做的最好的事情就是我在下面的答案中所做的:仅在测试中注入一个属性并检查它是否存在。效果很好,并且可靠地测试了我需要测试的内容。
【解决方案2】:

好的,我想出了一种方法来做到这一点,而无需通过添加注入属性的类别来修改缓存类的源。在MyThingTests.m 中,我将其添加到顶部:

#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

这会注入一个名为sentinel 的属性。当然,它只在测试中可用。使用它,测试变为:

@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

所以我可以判断sentinel 属性的存在与否创建了一个新对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-01
    • 2013-12-26
    • 2011-11-02
    相关资源
    最近更新 更多