10

我很好奇保留/释放在内部是如何工作的。从表面上看,似乎有一个与 an 的每个实例相关的整数NSObject,当您分别调用-retainand时,它会-release分别增加和减少。

但是看一下NSObject,它唯一的实例变量是isa变量,用于确定其类类型。

那么单个对象的保留计数存储在哪里?不是说我要乱搞,只是为了我自己的教化。

它是否与 一起存储NSObject,但隐藏在一些 Objective C 实现细节中?如果是这样,那对我来说似乎是一个糟糕的设计。一个人应该能够创建自己的根类并以类似的方式处理自己的保留/释放计数(并不是说这是一个好主意——必须有一个很好的理由不使用NSObject)。

4

5 回答 5

15

保留计数的存储位置取决于使用的运行时和类的实现。

对于 Apple 的 Objective-C 运行时,你可以通过深入研究 Objective-C 运行时的源代码来了解很多。

例如,如果您使用 ARC(我认为即使您没有使用),大多数对象的引用计数都存储在哈希表中。看看中的_objc_rootRetain函数runtime/objc-arr.mm。我不知道他们为什么这样做。也许这是一种将保留计数保持在一起以获得更好的缓存行为的方法(这在 ARC 下很重要,因为 ARC 比非 ARC 代码通常更频繁地调整保留计数)。

但是,某些类会覆盖retain相关方法并将保留计数存储在其他地方。例如,在调试内存泄漏时,我发现这样CALayer做了。a 不使用运行时的正常保留计数机制,而是CALayer将其保留计数存储在私有 C++ 实现对象中。这相当令人沮丧,因为这意味着 Instruments Allocations 工具不会记录CALayer对象的保留和释放。

于 2012-04-11T16:58:43.313 回答
7

我们不确切知道数据是如何存储的,但我们可以排除几个选项:

私有实现变量

我们可以排除这种情况,仅仅是因为当我们遍历NSObject类的 iVar 时,我们只看到一个: isa,如通过该程序所示:

id object = [NSObject new];
Class meta = object->isa;

printf("class name: %s\n", class_getName(meta));

unsigned count;
Ivar *ivars = class_copyIvarList(meta, &count);

for (int i = 0; i < count; i++) {
    printf("iVar: %s\n", ivar_getName(ivars[i]));
}

free(ivars);

请注意,甚至私有实现属性也存在于类元数据中。

私人财产

我们也可以排除这种情况,因为在类元数据中甚至公开了私有属性,如下例所示,NSObject该类没有属性:

id object = [NSObject new];    
Class meta = object->isa;

printf("class name: %s\n", class_getName(meta));

objc_property_t *properties = class_copyPropertyList(meta, &count);

for (int i = 0; i < count; i++) {
    printf("property: %s\n", property_getName(properties[i]));
}

关联对象

这个很难排除,因为没有直接的方法来获取所有关联对象的列表。但是,由于关联对象的概念非常新,并且引用计数一直存在,我说这不太可能。

CoreFoundation 结构修改

这是我最好的猜测。当您创建一个 NSObject 时,它是一个幕后结构。也就是说实际的 NSObject 数据表示是这样的:

typedef struct CFObject {
    int retainCount;
    id isa;
} *CFObjectRef;

然后,当创建一个对象时:

id object_createInstance(...)
{
    CFObjectRef object = malloc(sizeof(struct CFObject));

    ...

    return (id) (object + sizeof(object->retainCount));
}

int object_retainCount(id self)
{
    CFObjectRef asObject = (CFObjectRef) (self - sizeof(asObject->retainCount));
    return asObject->retainCount;
}

但是,我无法验证这一点,因为还有许多其他方法可以做到这一点(例如,整数到对象的映射)。

于 2012-04-11T16:44:03.843 回答
2

听起来不像,但以防万一……如果您想直接使用保留计数,请不要.

至于实现细节,WWDC 2011 的会议提到,在 ARC 下,大部分引用计数实现已经转移到 ObjC 运行时。它的来源是可用的,所以你也许可以自己找出它是如何工作的。对于手动引用计数,大部分 ObjC 行为都在 CoreFoundation 和 libdispatch 中复制,它们也是开源的——如果你想自己实现一个类似的方案,这些可能证明是有教育意义的。

一般来说,这是一个实现细节,原因与许多事情相同:封装是好的策略,尤其是对于框架提供者。您不希望框架的用户依赖于实现细节,因为这样您就无法在不破坏他们的代码的情况下更改您的实现。

于 2012-04-11T16:25:40.713 回答
0

如需更多见解,请查看http://www.mikeash.com/pyblog/friday-qa-2011-09-16-lets-build-reference-counting.html,Mike Ash 在其中探索了一种替代实现,例如 Apple 使用的实现.

于 2012-04-11T17:21:01.313 回答
0

不知道这是否相关,但我偶然发现了有关在 Objective-C 中实现高阶消息的博客文章。作者将 HOM 对象实现为根类(即不是从 NSObject 继承),实现如下所示:

@interface HigherOrderMessage {
    Class isa;
    NSUInteger retainCount;

    //not relevant to this question part
}

然后保留计数管理方法是这样实现的:

- (id)retain {
    __sync_add_and_fetch(&retainCount, 1);
    return self;
}

- (id)autorelease {
    [NSAutoreleasePool addObject:self];
    return self;
}

- (void)release {
    if (__sync_sub_and_fetch(&retainCount, 1) == 0) {
        [methodSignatureForSelector release];
        [forward release];
        object_dispose(self);
    }
}

这段代码确实有效,所以虽然我们不知道 Cocoa 类中的 retainCount 是如何实现的,但可以肯定的是,它可以以某种类似的方式实现。

于 2012-04-11T17:00:13.917 回答