草庐IT

ios - 内存泄漏 `NSLayoutConstraint`

coder 2024-01-28 原文

我有一个带有连接两个 View 的约束的简单布局,我将该约束存储在一个 strong 属性中。

我的预期是,在删除其中一个 View 后,约束仍然存在,但会变为非事件状态。但事实并非如此,我的应用程序因 EXC_BAD_ACCESS 异常而崩溃。

幸运的是我能够重现这个问题。我附上 View Controller 的源代码。您可以将其粘贴到使用“单 View 应用程序”模板创建的新项目中。

要重现问题,请在删除其中一个 View 之前和之后打印约束说明。它发生在我身上一次,约束没有被删除并且代码有效(但它只发生了一次)。使用调试器,我能够检查我删除的 View 是否也没有被释放,而它已经没有层,所以它的某些部分已经被解构了。发生这种情况后,我注释掉了 printConstraintDescriptionButtonTouchedUpInside 的实现,并在 removeViewButtonTouchedUpInside 中设置了断点。当调试器在第二次按下按钮后暂停应用程序时,约束不再存在。

是否不允许持有对 NSLayoutConstraint 实例的强引用?我没有在文档中找到该信息。

使用 Xcode 版本 10.1 (10B61) 编译,运行 iOS 模拟器 iPhone SE 12.1。在运行 iOS 12.1.3 (16D5032a) 和 11.2.2 (15C202) 的物理设备上,相同的逻辑(但不在孤立的代码段中)失败。使用 Xcode 版本 9.4.1 进行编译不会改变任何内容。

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) UIView* topView;
@property (strong, nonatomic) UIView* bottomView;

@property (strong, nonatomic) NSLayoutConstraint* constraint;

@property (strong, nonatomic) UIButton* removeViewButton;
@property (strong, nonatomic) UIButton* printConstraintDescriptionButton;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.topView = [UIView new];
    self.topView.backgroundColor = [UIColor grayColor];
    self.topView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:self.topView];

    self.bottomView = [UIView new];
    self.bottomView.backgroundColor = [UIColor grayColor];
    self.bottomView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:self.bottomView];

    self.removeViewButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.removeViewButton.translatesAutoresizingMaskIntoConstraints = NO;
    [self.removeViewButton setTitle:@"Remove View"
                           forState:UIControlStateNormal];
    [self.removeViewButton addTarget:self
                              action:@selector(removeViewButtonTouchedUpInside)
                    forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.removeViewButton];

    self.printConstraintDescriptionButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.printConstraintDescriptionButton.translatesAutoresizingMaskIntoConstraints = NO;
    [self.printConstraintDescriptionButton setTitle:@"Print Constraint Description"
                                           forState:UIControlStateNormal];
    [self.printConstraintDescriptionButton addTarget:self
                                              action:@selector(printConstraintDescriptionButtonTouchedUpInside)
                                    forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.printConstraintDescriptionButton];

    NSDictionary* views = @{
                            @"topView": self.topView,
                            @"bottomView": self.bottomView,
                            @"removeViewButton": self.removeViewButton,
                            @"printConstraintDescriptionButton": self.printConstraintDescriptionButton
    };

    NSArray<NSString*>* constraints = @[
                             @"H:|-[topView]-|",
                             @"H:|-[bottomView]-|",
                             @"H:|-[printConstraintDescriptionButton]-|",
                             @"H:|-[removeViewButton]-|",
                             @"V:|-[topView(==44)]",
                             @"V:[bottomView(==44)]",
                             @"V:[printConstraintDescriptionButton]-[removeViewButton]-|"
    ];

    [constraints enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:obj
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
    }];

    self.constraint = [NSLayoutConstraint constraintWithItem:self.topView
                                                   attribute:NSLayoutAttributeBottom
                                                   relatedBy:NSLayoutRelationEqual
                                                      toItem:self.bottomView
                                                   attribute:NSLayoutAttributeTop
                                                  multiplier:1.0
                                                    constant:-8.0];
    self.constraint.active = YES;
}

- (void)printConstraintDescriptionButtonTouchedUpInside {
    NSLog(@"%@", self.constraint);
}

- (void)removeViewButtonTouchedUpInside {
    [self.bottomView removeFromSuperview];
    self.bottomView = nil;
}

@end

最佳答案

问题不在于约束不再存在...问题在于:

NSLog(@"%@", self.constraint);

尝试打印约束的(默认)描述。如果约束未激活,则会抛出 EXC_BAD_ACCESS 异常(您无法使用 @try/catch block 捕获该异常)。

用这个替换你的方法:

- (void)printConstraintDescriptionButtonTouchedUpInside {

    NSLog(@"self.constraint still exists? %@", self.constraint != nil ? @"Yes" : @"No");

    NSLog(@"self.constraint.constant = %0.2f", self.constraint.constant);

    if (self.constraint.isActive) {
        NSLog(@"self.constraint (default description) %@", self.constraint);
    } else {
        // this will throw EXC_BAD_ACCESS exception if constraint is not active
        // so, don't do it
        //NSLog(@"self.constraint (default description) %@", self.constraint);
    }

}

您将看到前两个 NSLog() 执行得很好,无论约束是否处于事件状态。但是,如果您删除 bottomView(或以其他方式停用 self.constraint),您将在尝试访问 self 时收到错误消息.constraint.description(或 self.constraint.debugDescription)。

关于ios - 内存泄漏 `NSLayoutConstraint`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54635509/

有关ios - 内存泄漏 `NSLayoutConstraint`的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. ruby-on-rails - Ruby 中的内存模型 - 2

    ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序

  4. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. 键删除后 ruby​​ 哈希内存泄漏 - 2

    你好,我无法成功如何在散列中删除key后释放内存。当我从哈希中删除键时,内存不会释放,也不会在手动调用GC.start后释放。当从Hash中删除键并且这些对象在某处泄漏时,这是预期的行为还是GC不释放内存?如何在Ruby中删除Hash中的键并在内存中取消分配它?例子:irb(main):001:0>`ps-orss=-p#{Process.pid}`.to_i=>4748irb(main):002:0>a={}=>{}irb(main):003:0>1000000.times{|i|a[i]="test#{i}"}=>1000000irb(main):004:0>`ps-orss=-p

  7. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  8. ruby-on-rails - HTTParty 的内存问题和下载大文件 - 2

    这会导致Ruby出现内存问题吗?我知道如果大小超过10KB,Open-URI会写入TempFile。但是HTTParty会在写入TempFile之前尝试将整个PDF保存到内存吗?src=Tempfile.new("file.pdf")src.binmodesrc.writeHTTParty.get("large_file.pdf").parsed_response 最佳答案 您可以使用Net::HTTP。参见thedocumentation(特别是标题为“流媒体响应机构”的部分)。这是文档中的示例:uri=URI('http://e

  9. ruby-on-rails - 内存中具有相同 ID 的更多对象? - 2

    在部署在heroku上的Rails应用程序(v:3.1)中,我在内存中获得了更多具有相同ID的对象。我的heroku控制台日志:>>Project.find_all_by_id(92).size=>2>>ActiveRecord::Base.connection.execute('select*fromprojectswhereid=92').to_a.size=>1这怎么可能?可能是什么问题? 最佳答案 解决方案根据您的SQL查询,您的数据库中显然没有重复条目。也许您的类项目中的size或length方法已被覆盖。我试过find_

  10. ruby - 为 IO::popen 拯救 "command not found" - 2

    当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

随机推荐