1

I have a question about ARC in Swift, please take a look at the following code.

When SecondViewController is presented, the alert will pop up, and once you click "OK" button, SecondViewController will dismiss. Here is my question, in my opinion, SecondViewController retain UIAlertController, UIAlertController retain UIAlertAction, and UIAlertAction calls the function back() in SecondViewController. So it may cause retain cycle in this case, and it does cause retain cycle according the video here(around 8:30). However, when SecondViewController dismiss, the deinit function is called, and it does print SecondViewController is being deinitializing. So I'm confused which one is correct.

class SecondViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .orange
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let alert = UIAlertController(title: "Hi", message: "everyone", preferredStyle: .alert)
        let ok = UIAlertAction(title: "OK", style: .default) { _ in
            self.back()
        }

        alert.addAction(ok)
        self.present(alert, animated: true)
    }

    private func back() {
        dismiss(animated: true)
    }

    deinit {
        print("SecondViewController is being deinitializing")
    }

}

And here is another example. I run the following code in playground, and the deinit function is still called when I set test equals to nil. I don't understand why it would be called without adding [weak self] before the value in hehe() function.

class Test {

    var int: Int = 0

    func hehe() {
        fetchAPI(input: 3) { value in
            self.int = value
        }
    }

    func fetchAPI(input: Int, completion: @escaping (Int) -> Void) {
        completion(input)
    }

    deinit {
        print("Test in being deinitializing")
    }

}

var test: Test? = Test()

test?.hehe()

test = nil

I have taken a look at the Swift documentation, but couldn't find the similar example.

I expect the deinit function will not be called until I add [weak self]. I know ARC does not work on value type, but still not sure about it. Does anyone could explain more about this? Thanks.

[Update]

Here's the third example, and it will cause retain cycle. I guess the reason is because UIAlertController and UIAlertAction retains each other, and UIAlertAction also retains SecondViewController. So when UIAlertController dismiss, the closure in UIAlertAction is still exist, which cause retain cycle. Am I correct?

class SecondViewController: UIViewController {
    
    var array: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .orange
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let alertController = UIAlertController.init(title: "Test", message: nil, preferredStyle: .alert)
        alertController.addTextField(configurationHandler: nil)
        let alertAction = UIAlertAction.init(title: "OK", style: .default) { (action) in
            let text = alertController.textFields![0].text
            self.array.append(text!)
        }
        alertController.addAction(alertAction)
        self.present(alertController, animated: true, completion: nil)
    }

    deinit {
        print("SecondViewController is being deinitializing")
    }

}

1 Answer 1

3

First, checking memory management in a Playground is meaningless. Playgrounds operate in very unusual ways. You cannot explore anything related to memory, performance, threads, or system interactions in a Playground. They are purely for playing with algorithms and data structures.

That said, the first situation you're describing is expected. SecondViewController only retains UIAlertController for as long as it is presented (when it is dismissed, it is released). UIAlertAction only retains its callback until it calls it. After that it is released. This is a common pattern of holding onto objects you need until you no longer need them, then releasing them. That ensures the object exists for its lifecycle, and then can be destroyed.

Your Test example is fairly similar, but even simpler. The closure is released as soon as fetchAPI returns, since it's never assigned to anything. You marked it @escaping, but it doesn't actually escape.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks a lot for your reply! You mean the first example, the UIAlertController is released before SecondViewController dismiss, so it will not cause retain cycle? I still have a question, it seems like there's no need to add [weak self] in these 2 examples according to your answer, then when should I use it?
@Mark correct, you don't need [weak self]
Agreed. You don't need it here. You need it when there is a permanent loop that neither side will ever break. For example, if you were to pass an action that should be run any time an event happens (a button press for example). In that case there is nothing that will break the loop. But for callbakcs that are executed just one time (and that the holding object correctly releases), you generally don't need weak. People often apply it anyway without thinking through whether it is needed. And sometimes people make objects that fail to release their callbacks like they should.
I just added one more example in the edit version, am I correct with that one?
I'm not sure if you're right there or not in your edit. I'd use the Memory Graph tool to look at what's actually happening. developer.apple.com/documentation/xcode/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.