Home > Software design >  Xcode Visual Memory Debugger. What does it mean if some object is not released and this object and i
Xcode Visual Memory Debugger. What does it mean if some object is not released and this object and i

Time:07-19

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

enter image description here

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

  • Related