Why doesn't referencing outside of a function create retain cycle?

101 views Asked by At

Sorry, but I know this is a really dumb question, and I already kind of 'know' the answer, but I need someone to clearly explain to me WHY the answer is what it is.

Lately, I've become a bit obsessed/paranoid about retain cycles and memory leaks in my code, after going through some nightmarish debugging with various memory issues, so in the future I want to nip them in the bud. But after reading and learning a lot about ARC and retain cycles in Swift, although it makes sense, I still don't really have enough of an "intuitive" or natural feel for it, to feel confident that I could spot one, or the lack of one, as I'm coding. So I'm starting to become a little paranoid that I'm creating retain cycles with even basic stuff without realizing it.

So, with that in mind, why doesn't any ordinary function that uses a variable declared outside of it create a retain cycle? For example:

    class someClass {
        let a = "I'm letter a"
        let moreLetters = addLetters()
        func addLetters () -> String { 
            let newString = a + "bcdefg"
            return newString
        }
    }

In this case, self.moreLetters references the function addLetters, and then the constant self.a is references from within the function addLetters. So would this create a retain cycle if I don't capture weak/unowned self? It seems absurd to me that something this simple would cause a problem...or is it? What about in a nested function, like this:

    func someFunction () -> String {
        let a = "I'm letter a"
        func addLetters () -> String { 
            let newString = a + "bcdefg"
            return newString
        }
        let moreLetters = addLetters()
        return moreLetters
    }

Would that also create a retain cycle? (Yeah I know this is a convoluted way of performing a simple task; I'm just using this code as an example to make my point).

Have I become super-paranoid and am severely overthinking things?

1

There are 1 answers

1
Sweeper On BEST ANSWER

First, you need to understand how a basic retain cycle is formed. A retain cycle is formed when an object A refers to an object B strongly and at the same time. object B refers to object A strongly as well.

Let's look at your first bit of code.

class someClass {
    let a = "I'm letter a"
    let moreLetters = addLetters()
    func addLetters () -> String { 
        let newString = a + "bcdefg"
        return newString
    }
}

Actually, a class by itself can never create retain cycles, so let's add some code to create an object:

var obj = someClass()

First, a is initialized to "I'm letter a". After that, moreLetters is initialized by calling the method addLetters. After the method returns, moreLetters is initialized to "I'm letter abcdefg". So far so good.

Now we set obj to nil:

obj = nil

If a retain cycle were formed, obj would not be deinitialized. However, in actuality, obj is deinitialized because nothing holds a strong reference to obj!

"Wait a minute!" you say, "But the method addLetters still refers to someClass because it has a in it!" Well, in fact, addLetters has already returned! Therefore, everything in it doesn't matter anymore! In addition, addLetters belongs to obj, which you have already set to nil!

Now let's look at your second code:

func someFunction () -> String {
    let a = "I'm letter a"
    func addLetters () -> String { 
        let newString = a + "bcdefg"
        return newString
    }
    let moreLetters = addLetters()
    return moreLetters
}

A retain cycle does not form because there isn't a even a reference type! There is no objects to be created. All you did in the second code is playing with strings, which are value types. Even if there were a class, a retain cycle would not form because as I said, when you set obj to nil, all the methods in it "disappear" because methods belong to objects.


What about those closures where I must write [weak self] or a retain cycle forms?

Those closures are escaping closures. Normal closures are deinitialized after they return. However, escaping closures are retained by some object so they are not deinitialized immediately. For more info, see Escaping Closures in Swift