使用 ARC 查找对象的保留位置

Posted

技术标签:

【中文标题】使用 ARC 查找对象的保留位置【英文标题】:Find where object is retained with ARC 【发布时间】:2012-09-04 11:17:21 【问题描述】:

我有一个对象被保留超过必要(很可能是由于属性是strong 而不是weak)。代码库很大,所以很难找到。

如何在使用 ARC 时找到保留此对象的所有行?

如果我不使用 ARC,我想我可以简单地覆盖 retain 并检查它的调用位置。我可以用 ARC 做类似的事情吗?

【问题讨论】:

如果您有泄漏,为什么不使用 Instruments 进行追踪呢?还是静态分析器? 仪器和静态分析仪均未将其标记为泄漏。 @hpique 如果是保留周期,静态分析器将找不到它。分析器只会发现局部泄漏。如果您已经知道泄漏的存在,只需使用 Instruments 观察对象的生命周期。 @bbum 的评论:“-retainCount 从 ARC 中被弃用/消除是有原因的。它通常没有用,在可能有用的特定情况下,有一个更好的方式。retainCount 永远不能反映自动释放状态,也不能反映线程交互。所以,不,这个答案是不正确的。如果你想知道对象在哪里被保留/释放,请使用带有“记录引用计数”的分配工具。在那在上下文中,保留计数很有用,因为可用信息的完全保真度(触发它的回溯和线程)” 【参考方案1】:

为了跟踪应用程序的增长,Heapshot Analysis 已被证明非常有效。它将捕获真正的泄漏和内存的增加,其中分配不是由泄漏引起的。

您可以使用分配工具查看所有保留/释放事件及其回溯。点击分配工具上的小 (i) 按钮并打开“记录引用计数”。启用“仅跟踪活动分配”会减少 Instruments 收集的数据量,使其更加快速(在这种情况下,死分配并不是真正有用,但在其他情况下可能有用)。

这样,您可以深入了解任何分配(通过单击地址字段中的右箭头),查看所有保留/释放事件并准确查看它们发生的位置。

【讨论】:

Instruments 现在与 ios7/Xcode5 有点不同,但这仍然很适用。当然,在弄清楚这一切是如何工作的之后,我发现我以教科书的方式创建了一个保留循环(委托-委托人都强烈地相互引用)。 叹息 @RembrandtQ.Einstein 金句 :) 委托代理 - 强大.. :) 帮助了我 :) 现在这在 Instruments 中被称为“Generations”。您可能希望在 真实设备 上进行分析,因为模拟器不会象征自己的类名。 他们继续移动到选项所在的位置。在 Xcode 10 的 Instruments 中,引用计数选项位于 File => Recording Options... @adam.wulf 谢谢!我发现自己正在寻找它。【参考方案2】:

通过执行以下操作,我设法找到了有问题的 retain

    暂时将-fno-objc-arc添加到对象类Compiler Flags 为该类禁用 ARC。 暂时覆盖retain(只需调用super)并在其上放置断点。 每次调用retain 时调试并检查调用堆栈。

【讨论】:

一个非常有效的锤子来驱动螺丝... :) 这很有效——我在极端情况下使用过它——但是启用“记录引用计数”的分配工具应该能够在不修改代码的情况下向您展示这一点(包括可能引入其他错误的内存模型更改)。【参考方案3】:

上周我正在帮助一些朋友在他们的 ARC 项目中调试漏洞。 一些提示:

1/ 构建分析并启动带有泄漏检测的仪器。然后探索当前分配的对象,找到您想要的对象(您可以按名称对其进行排序)并查看其保留/释放历史记录。请注意,保留计数对 ARC 不是很有帮助。您必须手动逐步检查。

尝试注释所有可能成为泄漏源的代码,然后逐步取消注释。

2/ 将NSLog 放入您的init 和您的dealloc 以观察对象的创建和销毁时间。

3/ 不要只看属性定义,观察属性设置器是否手动实现。我在朋友的项目中发现了一个问题,如下所示:

@property (weak, nonatomic) id<...> delegate;

@interface ... 
    id<...&gt _delegate;


@synthesize delegate = _delegate;

- (void)setDelegate(id<...>)delegate 
    _delegate = delegate;  //with ARC this retains the object!

【讨论】:

我会先结合使用 Heapshot 分析和使用 Allocations Instrument 打开参考帐户事件记录。您的答案没有错,只是鉴于 Instruments 的功能可能不需要它。 @bbum 分配会在泄漏时自动开启。 是的——泄漏检测对所有人都是无用的,只能发现最严重的问题。 Heapshot 分析和/或跟踪引用计数也将捕获 所有 泄漏和所有其他内存增加。只是澄清一点。 @Sulthan 为什么在您的属性示例中保留该对象? @Sulthan 我看不出 3/ 有什么问题。 ARC 不应该保留对象,因为它很弱。欲了解更多信息:***.com/questions/15607404/…【参考方案4】:

这个解决方案对我有点帮助。它基本上使用方法调配来欺骗 ARC 编译器,使其认为您没有覆盖保留和释放。

#import <objc/runtime.h>
...
+ (void)load 
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        Class cls = [self class];
        
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        
        SEL originalSelector1 = NSSelectorFromString(@"retain");
        SEL swizzledSelector1 = NSSelectorFromString(@"myretain");
        
        SEL originalSelector2 = NSSelectorFromString(@"release");
        SEL swizzledSelector2 = NSSelectorFromString(@"myrelease");
        
        Method originalMethod1 = class_getInstanceMethod(cls, originalSelector1);
        Method swizzledMethod1 = class_getInstanceMethod(cls, swizzledSelector1);
        Method originalMethod2 = class_getInstanceMethod(cls, originalSelector2);
        Method swizzledMethod2 = class_getInstanceMethod(cls, swizzledSelector2);
        
        BOOL didAddMethod1 =
        class_addMethod(cls,
                        originalSelector1,
                        method_getImplementation(swizzledMethod1),
                        method_getTypeEncoding(swizzledMethod1));
    
        if (didAddMethod1) 
            class_replaceMethod(cls,
                                swizzledSelector1,
                                method_getImplementation(originalMethod1),
                                method_getTypeEncoding(originalMethod1));
         else 
            method_exchangeImplementations(originalMethod1, swizzledMethod1);
        
    
        BOOL didAddMethod2 =
        class_addMethod(cls,
                        originalSelector2,
                        method_getImplementation(swizzledMethod2),
                        method_getTypeEncoding(swizzledMethod2));

        if (didAddMethod2) 
            class_replaceMethod(cls,
                                swizzledSelector2,
                                method_getImplementation(originalMethod2),
                                method_getTypeEncoding(originalMethod2));
         else 
            method_exchangeImplementations(originalMethod2, swizzledMethod2);
        
    );


-(id)myretain 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    NSLog(@"tracking retain now %@",@((uintptr_t)[self performSelector:NSSelectorFromString(@"retainCount")]));
    SEL selector = NSSelectorFromString(@"myretain");
    return [self performSelector:selector withObject:nil];
#pragma clang diagnostic pop


-(id)myrelease 
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    NSLog(@"tracking release now %@", @((uintptr_t)[self performSelector:NSSelectorFromString(@"retainCount")]));

    SEL selector = NSSelectorFromString(@"myrelease");
    return [self performSelector:selector withObject:nil];
#pragma clang diagnostic pop

【讨论】:

别忘了包含#import 【参考方案5】:

如果您使用 ARC,您将永远无法选择添加保留,

如果您已使用以下选项将项目转换为 ARC,则会提示您出错

如果您将属性设置为strong,那么您应该在整个项目中分配一次对象,例如self.yourobject = [[NSMutableArray alloc]init];。没有捷径可走。

【讨论】:

我找到了一个快捷方式。看我的回答。

以上是关于使用 ARC 查找对象的保留位置的主要内容,如果未能解决你的问题,请参考以下文章

ARC 会在啥条件下保留该对象?

for 循环中的强类对象未保留在 ARC 中

如何使用屏幕上的位置(边界)在 UiAutomator 中查找对象

为啥在启用 ARC 的项目中不需要维护保留计数

ARC 和桥接铸件

当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?