Почему следующий код не вызывает утечку памяти

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

import UIKit

class ViewController: UIViewController {

    private var button = {
        let _button = UIButton()
        _button.setTitle("Tap Me", for: .normal)
        _button.setTitleColor(.systemBlue, for: .normal)
        return _button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(button)
        
        button.addTarget(self, action: #selector(actionButton), for: .touchUpInside)
        
        button.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.widthAnchor.constraint(equalToConstant: 80),
            button.heightAnchor.constraint(equalToConstant: 30)
        ])
    }
    
    @objc private func actionButton() {
        
        let secondVC = ViewController2()
        
        present(secondVC, animated: true)
    }

}

class MyView: UIView {
    
    let viewContoller: ViewController2
    
    init(from vc: ViewController2) {
        viewContoller = vc
        
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

class ViewController2: UIViewController {
    
    var myView: MyView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .red
        
        myView = MyView(from: self)
    }
    
}

На ViewController есть кнопка, при нажатии которой открывается новый контроллер представления ViewController2, который инициализирует UIView, и когда я закрываю этот контроллер представления (ViewController2), он должен создать цикл сохранения (поскольку представление также содержит объект ViewController2). Где именно я ошибаюсь?

Редактировать 1: я попробовал deinit внутри ViewController2, и он сработал, как и ожидалось. Однако путаница началась, когда я попытался профилировать тот же код в приложении «Инструменты», и оно показало «Нет утечек», все время были отмечены зелеными галочками.

Вы ссылаетесь на инструмент «Leaks» компании Instruments. Это делается для поиска низкоуровневых выделений, на которые у вас больше нет ссылок, но которые впоследствии не удалось освободить. Он не предназначен и не будет идентифицировать «сильные опорные циклы» (ранее известные как «циклы сохранения»). Кстати, настоящие «утечки» чрезвычайно необычны в Swift, если только не используются методы низкоуровневого использования памяти. График памяти отладки — это метод «перехода» для выявления сильных ссылочных циклов.

Rob 12.08.2024 20:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы успешно создали цикл хранения. Ваша единственная проблема — как это обнаружить. Чтобы обнаружить цикл сохранения, добавьте эти строки в MyView и ViewController2 соответственно:

deinit {
    print("farewell from MyView")
}

deinit {
    print("farewell from ViewController2")
}

Теперь запустите приложение, нажмите кнопку и закройте (перетаскивая) представленный контроллер представления. На консоль ничего не выводится. Таким образом, мы знаем, что ни ViewController2, ни MyView не освобождаются. Это утечка.


Это также поможет создать контрастную ситуацию, чтобы вы могли видеть, что «прощальные» сообщения будут печататься, если не будет цикла сохранения. Для этого измените MyView следующим образом:

let viewContoller: ViewController2?

init(from vc: ViewController2?) {
    viewContoller = vc

    super.init(frame: .zero)
}

Теперь измени

    myView = MyView(from: self)

к

    myView = MyView(from: nil)

Запустите приложение, нажмите кнопку и закройте (перетаскивая) представленный контроллер представления. Сообщения deinit распечатываются! Таким образом, мы создали красно-зеленый тест, демонстрирующий цикл сохранения. Вы можете изменить код обратно на

    myView = MyView(from: self)

и подтвердите, что сообщение deinit не печатается. Получайте удовольствие, играя с новым демонстрационным приложением цикла сохранения!


Также возможно обнаружить цикл с помощью функции графика памяти. Обратите внимание на бесконечную цепочку (у меня перестали открываться пуанты после первого повторения):

Спасибо за ответ. Я попробовал начать с deinit, и он работал так, как ожидалось. Однако путаница началась, когда я попробовал инструмент «Утечки» в приложении «Инструмент», и он показал «Нет утечек» для кода. Есть идеи, почему это может быть так?

user121095 12.08.2024 16:52

Мне кажется, это другой вопрос, так что теперь вы меняете свою точку зрения. Вы не упомянули deinit в исходном вопросе; вы спросили, делаете ли вы что-то не так, пытаясь создать цикл сохранения, и, кажется, я ответил на этот вопрос. Если вы считаете, что с инструментом Instruments Leaks возникла проблема, вы можете спросить об этом отдельно и/или отправить отчет об ошибке в Apple.

matt 12.08.2024 19:04

Фактически вы создаете цикл сохранения, вы можете либо проверить график памяти отладки после закрытия ViewController2, и экземпляр будет там: 1

Или вы можете реализовать метод deinit и записать что-нибудь:

deinit { print("Instance removed from memory") }

В любом случае, с помощью предоставленного вами кода экземпляр будет отображаться на графике памяти, и метод deinit не будет вызываться ни в представлении, ни в контроллере 2.

Чтобы разорвать порочный круг, вам понадобится слабая (или бесхозная) ссылка, например:

weak var viewContoller: ViewController2?

Спасибо за ответ. Я попробовал начать с deinit, и он работал так, как ожидалось. Однако путаница началась, когда я попробовал инструмент «Утечки» в приложении «Инструмент», и он показал «Нет утечек» для кода. Есть идеи, почему это может быть так?

user121095 12.08.2024 16:52

Другие вопросы по теме