Я читал цикл сохранения и пробовал следующий код, который теоретически должен вызывать утечку памяти, но когда я использую инструменты, он не показывает никаких утечек памяти.
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
, и он сработал, как и ожидалось. Однако путаница началась, когда я попытался профилировать тот же код в приложении «Инструменты», и оно показало «Нет утечек», все время были отмечены зелеными галочками.
Вы успешно создали цикл хранения. Ваша единственная проблема — как это обнаружить. Чтобы обнаружить цикл сохранения, добавьте эти строки в 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
, и он работал так, как ожидалось. Однако путаница началась, когда я попробовал инструмент «Утечки» в приложении «Инструмент», и он показал «Нет утечек» для кода. Есть идеи, почему это может быть так?
Мне кажется, это другой вопрос, так что теперь вы меняете свою точку зрения. Вы не упомянули deinit
в исходном вопросе; вы спросили, делаете ли вы что-то не так, пытаясь создать цикл сохранения, и, кажется, я ответил на этот вопрос. Если вы считаете, что с инструментом Instruments Leaks возникла проблема, вы можете спросить об этом отдельно и/или отправить отчет об ошибке в Apple.
Фактически вы создаете цикл сохранения, вы можете либо проверить график памяти отладки после закрытия ViewController2
, и экземпляр будет там: 1
Или вы можете реализовать метод deinit
и записать что-нибудь:
deinit { print("Instance removed from memory") }
В любом случае, с помощью предоставленного вами кода экземпляр будет отображаться на графике памяти, и метод deinit
не будет вызываться ни в представлении, ни в контроллере 2.
Чтобы разорвать порочный круг, вам понадобится слабая (или бесхозная) ссылка, например:
weak var viewContoller: ViewController2?
Спасибо за ответ. Я попробовал начать с deinit
, и он работал так, как ожидалось. Однако путаница началась, когда я попробовал инструмент «Утечки» в приложении «Инструмент», и он показал «Нет утечек» для кода. Есть идеи, почему это может быть так?
Вы ссылаетесь на инструмент «Leaks» компании Instruments. Это делается для поиска низкоуровневых выделений, на которые у вас больше нет ссылок, но которые впоследствии не удалось освободить. Он не предназначен и не будет идентифицировать «сильные опорные циклы» (ранее известные как «циклы сохранения»). Кстати, настоящие «утечки» чрезвычайно необычны в Swift, если только не используются методы низкоуровневого использования памяти. График памяти отладки — это метод «перехода» для выявления сильных ссылочных циклов.