NSLayoutConstraint导致内存泄漏

时间:2019-02-11 16:59:13

标签: ios objective-c memory-leaks autolayout

我有一个简单的布局,带有一个连接两个视图的约束,并将该约束存储在一个强大的属性中。

我的期望是,在删除这些视图之一之后,约束将仍然存在,但是它将变为非活动状态。但事实并非如此,我的应用因EXC_BAD_ACCESS异常而崩溃。

幸运的是,我能够重现该问题。我附上了视图控制器的源代码。您可以将其粘贴到使用“ Single View App”模板创建的新项目中。

要重现该问题,请在删除其中一个视图之前和之后打印约束描述。在我身上发生了一次,没有删除约束并且代码起作用了(但是只发生了一次)。使用调试器,我可以检查删除的视图是否也没有解除分配,尽管它没有图层,所以其中的某些部分已经被解构。发生之后,我注释了printConstraintDescriptionButtonTouchedUpInside的实现,只是在removeViewButtonTouchedUpInside中设置了断点。当调试器第二次按下按钮后暂停应用程序时,约束不再存在。

是否不允许强烈引用NSLayoutConstraint实例?我尚未在文档中找到该信息。

已与运行iOS Simulator iPhone SE 12.1的Xcode版本10.1(10B61)一起编译。在运行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

1 个答案:

答案 0 :(得分:0)

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

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

尝试打印约束的(默认) 描述 。如果约束不处于活动状态,则将引发EXC_BAD_ACCESS异常(您无法使用@ try / catch块捕获该异常)。

为此替换您的方法:

- (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 )。