0

I need some help with this problem.

I have this basic Scheme

  • HomeController
    • TripNavigation (view controller parent)
      • Step1 (view controller child)
      • Step2 (view controller child)

When i tap 10 times the button (buttonTapped) in homecontroller and then dismiss everytime.

  • In ui view hierarchy don't appear TripNavigation, which is good because i dismiss the controller and childs
  • In View Memory Graph hierarchy: enter image description here

I think i create a cycle but i don't found it.

My code:

Home Controller:

class HomeController: UIViewController {
    
    var user: User?
    init(user: User) {
        self.user = user
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // show button on view
        
    }
    
    @objc func buttonTapped{
        DriverService.shared.observeTrip(uid: "XXX") { (userDB) in
            let controller = TripNavigation(user: userDB)
            controller.modalPresentationStyle = .fullScreen
            self.present(controller, animated: true, completion: nil)
        }
    }
    
}

TripNavigation code:

protocol TripProtocol: AnyObject {
    func userHasChanged(_ user: User?)
}
class TripNavigation: UIViewController {

    var user: User
    weak var delegate: TripProtocol?
    // array of childs
    var listOfSteps: [UIViewController] = []
    // segmented control to navigate in childs
    let mySegmentedControl: UISegmentedControl = {
        let sc = UISegmentedControl(items: ["1","2"])
        return sc
    }()
    
    init(user: User) {
        self.user = user
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        loadviews()
        
        // here i make a addsubview for a button that call dissmisView() function (bottom of this class)
        // i call dissmisView to test if memory deallocated
        
    }

    func checkFirebaseNews(){
       // check new info from firebase and pass new data to childs
       self.delegate?.userHasChanged(user)
    }
    
    // main function that load the childs
    func loadviews(){
        
        mySegmentedControl.frame = CGRect(x: 0, y: 95, width: view.frame.width, height: 30)
        mySegmentedControl.addTarget(self, action: #selector(self.segmentedValueChanged(_:)), for: .valueChanged)
        view.addSubview(mySegmentedControl)
       
        // first child of TripNavigation
        let step1 = Step1(user: user, nextStep: 1)
        self.addChild(step1)
        step1.willMove(toParent: self)
        view.addSubview(step1.view)
        step1.didMove(toParent: self)
        step1.view.anchor(top:mySegmentedControl.topAnchor,left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 31)
        delegate = step1
        listOfSteps = [step1]
    
        // second child of TripNavigation
        let step2 = Step2(user: user, nextStep: -1)
        self.addChild(step2)
        step2.willMove(toParent: self)
        view.addSubview(step2.view)
        step2.didMove(toParent: self)
        step2.view.anchor(top:mySegmentedControl.topAnchor,left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 31)
        delegate = step2
        listOfSteps.append(step2)

        
        mySegmentedControl.selectedSegmentIndex = 0
        showView(num: 0)
        
        
    }
    
    // segmented control manage
    @objc func segmentedValueChanged(_ sender:UISegmentedControl!){

        switch sender.selectedSegmentIndex{
        case 0:
            
            showView(num: 0)
            
        case 1:
           
            showView(num: 1)

        default:
            break
        }
        
    }
    
    
    // aux functions
    func showView(num: Int){
        hideRestofViews(actual: num)
        let step = listOfSteps[num]
        step.view.isHidden = false
    }
    func hideRestofViews(actual: Int){
        if listOfSteps.count >= 1 {
            for (index, v) in listOfSteps.enumerated() {
                if index != actual{
                    v.view.isHidden = true
                }
            }
        }
    }
    
    
    
    
    @objc func dissmisView() {
      
        print("<W call to dismissview")
        for v in listOfSteps {
            v.willMove(toParent: nil)
            v.view.removeFromSuperview()
            v.removeFromParent()
            print(v)
            v.dismiss(animated: false, completion: nil)
            print("1")
        }
        
        print("<W all deleted")
        self.dismiss(animated: true, completion: nil)
        
    }
    
    // called in child, to change to other view child
    @objc func changeView(notification: NSNotification){
        
        if let next = notification.object as? Int {
            
            if next == -1{
                dissmisView()
            }else{
                mySegmentedControl.selectedSegmentIndex = next
                showView(num: next)
            }
            
        }
        
    }
    
    
}

Example of Step1 child code:

// Step1 and Step2 are practically the same
class Step1: UIViewController,TripProtocol {

    var mapView = MKMapView()
    var user: User?
    var nextStep: Int
    
    func userHasChanged(_ user: User?) {
        self.user = user
    }
    
    
    init(user: User?, nextStep: Int) {
        self.user = user
        self.nextStep = nextStep
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        //configureMapView() ... configureUI... that show user data
        
    }
    
    @objc func nextStep(){
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "changeView"), object: nextStep)
    }
   
    
}

Thanks so much everyone!!

EDIT @ROB HELP enter image description here

nacho1111
  • 73
  • 1
  • 8
  • 1
    The “debug memory graph” (especially with the “Malloc stack” option) will help you pinpoint what is keeping the strong reference to your dismissed view controllers. See https://stackoverflow.com/questions/30992338/how-to-debug-memory-leaks-when-leaks-instrument-does-not-show-them. – Rob Feb 19 '22 at 19:43
  • 1
    Click on a dismissed view controller that you see in the panel on the left that you think shouldn’t be there and you’ll see what is keeping the strong reference to it. – Rob Feb 19 '22 at 20:06
  • 1
    But `dissmisView` [sic] is incorrect. First you are both dismissing and unwinding view controller containment. Do one or the other, but not both. If you presented, then dismiss. If you added via containment, then remove via containment. Second, after doing all of that dismissal stuff, you leave them in your `listOfSteps` array. They won’t be deallocated until you remove them from your array, too. – Rob Feb 19 '22 at 20:20
  • thanks in advance for your help @rob, I have tried first of all doing a `removeAll()` of `listofSteps` and it reduces memory significantly (NEW PICTURE IN EDIT). but unfortunately the `TripNavigation` instances are still in memory. I will investigate well the link you have sent me, and I will continue investigating Xcode tools. Your help has been incredible! – nacho1111 Feb 19 '22 at 20:46

1 Answers1

0

You can add the "TripNavigation" View Controllers to an array object in the HomeController then assign them to nil when dismissing them.

check this question: iOS view controller memory not released after it's been dismissed

  • Thanks @mari for your answer. I need to call delegate from TripNavigation to HomeController when dismiss TripNavigation? I dont understand very well. – nacho1111 Feb 19 '22 at 13:06
  • The problem is that i can have more than one TripNavigation on my home controller. With different users in each one. But when i open one trip navigation and then i dismiss , i want kill the instance of that view controller and all childs – nacho1111 Feb 19 '22 at 15:03