When I close all VC and go back to my root I see that some element views and cells, like HeaderViewNew, DisposalsShimmering, exist in a heap, these elements are declared like lazy var
, why is this element not released from memory if I close VC that use it, it is normal, or I did some mistake?
I know that if I have a few examples of VC in memory this is a retail cycle, but with this element inside the heap hierarchy I am a little confused, and using info inside the Memory graph (main window) I can't solve this problem
Example of an object that has not been released:
import UIKit
protocol HeaderMenuOptionSelected: AnyObject {
func menuSelected(index: Int, title: String)
}
class HeadeViewMenu: UIView, UITableViewDelegate, UITableViewDataSource {
private var hiddenRows: Array<Int> = Array()
private var cellSize: CGFloat = 60
var selectedIndex: Int!
var tabBarHeight: CGFloat!
var tabBar: UIView!
let tabBarView = UIView()
var menuHeight: CGFloat = 0
var P_menuHeightConstraint: NSLayoutConstraint!
var menuArr: Array<HeaderMenuListOfOptions> = Array()
weak var delegate: HeaderMenuOptionSelected?
func clearMenuOptions() {
menuArr.removeAll()
P_menu.reloadData()
}
func setupListOfOptions(menuList: [HeaderMenuListOfOptions]) {
menuArr = menuList
P_menu.reloadData()
}
func setupMenuHeight(countOfElement: Int) {
menuHeight = cellSize * CGFloat(countOfElement)
}
// This trick need to use because if user open menu but user level not yet applied, after fetching setting from server need to update menu in menu open, but only ONE time
private var firstTimeOpen = true
func updateMenuHeight() {
guard firstTimeOpen != false else { return }
firstTimeOpen = false
if (P_menuHeightConstraint.constant != 0) {
menuHeight = cellSize * CGFloat(menuArr.count)
P_menuHeightConstraint.constant = menuHeight
UIView.animate(withDuration: 0.2) {
self.layoutIfNeeded()
} completion: { anim in
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
menuArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HeaderMenuCell
// iOS way to set default text label
// var content = cell.defaultContentConfiguration()
// content.text = "test"
// cell.contentConfiguration = content
cell.P_label.text = menuArr[indexPath.row].title
cell.selectionStyle = .none
cell.P_label.isHidden = needHideElement(index: indexPath.row)
cell.P_img.isHidden = needHideElement(index: indexPath.row)
cell.P_countOfElements.isHidden = needHideElement(index: indexPath.row)
if selectedIndex == indexPath.row {
cell.P_img.image = UIImage(named: menuArr[indexPath.row].selectedImg)
cell.P_label.textColor = menuArr[indexPath.row].selectedColor
cell.initCount(count: menuArr[indexPath.row].count)
} else {
cell.P_img.image = UIImage(named: menuArr[indexPath.row].img)
cell.P_label.textColor = menuArr[indexPath.row].unselectedColor
cell.initCount(count: menuArr[indexPath.row].count)
}
return cell
}
func openPageByImitationGesture(index: Int) {
hideMenu()
delegate?.menuSelected(index: index, title: menuArr[index].title)
selectedIndex = index
P_menu.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("index menu \(indexPath.row)")
hideMenu()
// FeedbackGenerator.shared.interactionFeedback()
// guard selectedIndex != indexPath.row else { return }
delegate?.menuSelected(index: indexPath.row, title: menuArr[indexPath.row].title)
// let cell = tableView.cellForRow(at: indexPath) as! HeaderMenuCell
selectedIndex = indexPath.row
tableView.reloadData()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return getRowHeight(index: indexPath.row)
}
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
print("index unhi....\(indexPath.row)")
}
lazy var P_menu: UITableView = {
let view = UITableView()
view.translatesAutoresizingMaskIntoConstraints = false
view.delegate = self
view.dataSource = self
view.register(HeaderMenuCell.self, forCellReuseIdentifier: "cell")
view.isScrollEnabled = false
view.separatorColor = .clear
view.separatorStyle = .none
return view
}()
lazy var P_menuView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.2
view.layer.shadowOffset = CGSize(width: 0, height: 5)
view.layer.shadowRadius = 2
return view
}()
lazy var P_backgroundView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.translatesAutoresizingMaskIntoConstraints = false
view.alpha = 0
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickAtBackgroundView))
tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1
view.addGestureRecognizer(tapGesture)
view.isUserInteractionEnabled = true
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
setupView()
}
deinit {
print("deinit called NewHeaderMenu")
}
func setupView() {
self.isHidden = true
self.addSubview(P_backgroundView)
self.addSubview(P_menuView)
self.P_menuView.addSubview(P_menu)
P_backgroundView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
P_backgroundView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
P_backgroundView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
P_backgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
P_menu.topAnchor.constraint(equalTo: P_menuView.topAnchor).isActive = true
P_menu.leadingAnchor.constraint(equalTo: P_menuView.leadingAnchor).isActive = true
P_menu.trailingAnchor.constraint(equalTo: P_menuView.trailingAnchor).isActive = true
P_menu.bottomAnchor.constraint(equalTo: P_menuView.bottomAnchor).isActive = true
P_menuView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
P_menuView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
P_menuView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
P_menuHeightConstraint = P_menuView.heightAnchor.constraint(equalToConstant: 0)
P_menuHeightConstraint.isActive = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func openMenu() {
self.isHidden = false
UIView.animate(withDuration: 0.1) {
self.P_backgroundView.alpha = 0.2
self.hideTabBar()
} completion: { anim in
if (anim == true) {
self.P_menuHeightConstraint.constant = self.menuHeight
UIView.animate(withDuration: 0.2) {
self.layoutIfNeeded()
} completion: { anim in
}
}
}
}
func hideMenu() {
P_menuHeightConstraint.constant = 0
UIView.animate(withDuration: 0.2) {
self.layoutIfNeeded()
} completion: { anim in
if (anim == true) {
UIView.animate(withDuration: 0.1) {
self.P_backgroundView.alpha = 0
self.showTabBar()
} completion: { anim in
if (anim == true) {
self.isHidden = true
}
}
}
}
}
func needToHideRows(rows: [Int]) {
hiddenRows = rows
let countOfElements = (menuArr.count - rows.count)
print("Menu count of elements \(countOfElements)")
setupMenuHeight(countOfElement: countOfElements)
P_menu.reloadData()
}
func unhideAllElements(count: Int) {
hiddenRows = []
setupMenuHeight(countOfElement: count)
P_menu.reloadData()
}
func getRowHeight(index: Int) -> CGFloat {
var size: CGFloat = menuHeight / CGFloat(menuArr.count - hiddenRows.count)
hiddenRows.forEach { obj in
if (index == obj) {
size = 0
}
}
return size
}
func needHideElement(index: Int) -> Bool {
var needToHide = false
hiddenRows.forEach { obj in
if (index == obj) {
needToHide = true
}
}
return needToHide
}
func setupTabBar(view: UIView) {
guard tabBarHeight != nil else {
return
}
tabBar = view
tabBar.addSubview(tabBarView)
tabBarView.translatesAutoresizingMaskIntoConstraints = false
tabBarView.backgroundColor = .black
tabBarView.heightAnchor.constraint(equalToConstant: tabBarHeight).isActive = true
tabBarView.leadingAnchor.constraint(equalTo: tabBar.leadingAnchor).isActive = true
tabBarView.trailingAnchor.constraint(equalTo: tabBar.trailingAnchor).isActive = true
tabBarView.bottomAnchor.constraint(equalTo: tabBar.bottomAnchor).isActive = true
tabBarView.alpha = 0
}
func hideTabBar() {
UIView.animate(withDuration: 0.1) {
self.tabBarView.alpha = 0.2
}
}
func showTabBar() {
UIView.animate(withDuration: 0.1) {
self.tabBarView.alpha = 0
}
}
@objc func clickAtBackgroundView() {
hideMenu()
delegate?.menuSelected(index: selectedIndex, title: menuArr[selectedIndex].title)
}
func selectOptionNonProgrammatically(index: Int) {
// This function need for fixind problem when user open app and app had list of sevec filters for disposal tab
hideMenu()
// delegate?.menuSelected(index: index, title: title)
delegate?.menuSelected(index: index, title: menuArr[index].title)
}
func updateCounter(index: Int, count: Int?) {
guard count != nil && count != 0 else { return }
menuArr[index].count = count
P_menu.reloadData()
}
}
Inside VC that contained this element I do next:
1. Init
lazy var P_headerViewNewMenu: HeadeViewMenu = {
let view = HeadeViewMenu()
view.delegate = self
return view
}()
2. Deinit
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
P_headerViewNewMenu.delegate = nil
}
3. Init constraints
self.view.addSubview(P_headerViewNewMenu)
P_headerViewNewMenu.topAnchor.constraint(equalTo: P_headerViewNew.bottomAnchor).isActive = true
P_headerViewNewMenu.leadingAnchor.constraint(equalTo: P_headerViewNew.leadingAnchor).isActive = true
P_headerViewNewMenu.trailingAnchor.constraint(equalTo: P_headerViewNew.trailingAnchor).isActive = true
P_headerViewNewMenu.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
P_headerViewNewMenu.tabBarHeight = self.tabBarController?.tabBar.frame.height
P_headerViewNewMenu.setupTabBar(view: (self.tabBarController?.view)!)
CodePudding user response:
I found the problem with be help of matt, years of experience of asking the right question ;) Problem was inside P_headerViewNewMenu.setupTabBar(view: (self.tabBarController?.view)!)
link to the object was not released.
Because VC was deallocated, and all objects continue to be in the heap, I haven't been understood that these are all objects that not called deinit, but VC was released from memory