Утечка памяти с помощью NSLayoutConstraint - PullRequest
0 голосов
/ 11 февраля 2019

У меня есть простой макет с ограничением, соединяющим два представления, и я сохраняю это ограничение в сильном свойстве.

Я ожидаю, что после удаления одного из этих представлений ограничение все еще будет существовать, но оно будетвыключитьНо это не тот случай, и мое приложение вылетает с исключением EXC_BAD_ACCESS.

К счастью, я могу воспроизвести проблему.Я прилагаю исходный код контроллера представления.Вы можете просто вставить его в новый проект, созданный с помощью шаблона «Single View App».

Чтобы воспроизвести проблему, напечатайте описание ограничения до и после удаления одного из представлений.Однажды со мной случилось, что ограничение не было снято, и код работал (но это случилось только один раз).С помощью отладчика я смог проверить, что удаленное представление также не было освобождено, хотя у него уже не было слоя, поэтому некоторые его части уже были деконструированы.После того, как это произошло, я прокомментировал реализацию printConstraintDescriptionButtonTouchedUpInside и просто установил точку останова в removeViewButtonTouchedUpInside.Когда отладчик приостановил приложение после повторного нажатия кнопки, ограничение больше не существовало.

Не разрешено ли хранить строгую ссылку на экземпляры NSLayoutConstraint?Я не нашел этой информации в документации.

Скомпилировано с версией Xcode 10.1 (10B61), работающей под управлением iOS Simulator 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

1 Ответ

0 голосов
/ 11 февраля 2019

Проблема не в том, что ограничение больше не существует ... проблема в том, что:

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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...