Will closure capture local variable in the method to course retained cycle?

the closure captures someEnum and someClass, will it cause retained cycle?

final class SearchBar {
    var tapped: () -> Void = {}
}

enum SomeEnum { case unknown }
class SomeClass {}

class SomeViewController {
    let searchBar = SearchBar()

    func setupSearchBar() {
        
        let someEnum = SomeEnum.unknown
        let someClass = SomeClass()

        searchBar.tapped = {
            print(someEnum)
            print(someClass)
        }
    }
}

No it won't as the closure searchBar.tapped doesn't have a strong reference to SomeViewController, searchBar.tapped holds a reference to someClass only (someEnum is a value type so it doesn't contribute to reference counting).

Here's an Xcode Memory Graph for the code you shared showing no retain cycles.

1 Like

in addition to what was pointed out above with the memory graph, i think the specific case you've presented can never admit a retain cycle. since the SomeEnum and SomeClass types store nothing, any 'reference graph' involving them will have no 'edges' to anything else from those nodes. thus there could be no reference cycle of any sort that contains them.

if these properties were referenced indirectly through another type however, a cycle could be created. e.g. if we modify your example to store the enum as a property on the view controller, then the following would create a cycle:

class SomeViewController {
    let searchBar = SearchBar()
    let someEnum = SomeEnum.unknown

    func setupSearchBar() {
        
        let someClass = SomeClass()

        searchBar.tapped = {
            print(self.someEnum) // now creates a cycle: self -> searchBar -> tapped -> self
            print(someClass)
        }
    }
}

edit: and a further digression, but since it has been on my mind recently, i will also highlight a couple potential footguns that are possible here involving partially applied function references. e.g.

class SomeViewController {
  let searchBar = SearchBar()
  let someEnum = SomeEnum.unknown

  func getSomeEnum() -> SomeEnum { SomeEnum.unknown }

  func setupSearchBar() {
    var localComputedSelfRef: SomeEnum {
      self.someEnum
    }
    let partialApplySomeEnum = getSomeEnum

    searchBar.tapped = {
      // capturing either the local computed var or
      // the partially applied instance method will
      // indirectly capture/retain self as well, creating
      // a cycle, and preventing deinit from running
      print(localComputedSelfRef)
      print(partialApplySomeEnum())
    }
    searchBar.tapped()
  }

  deinit {
    print("died")
  }
}

func test() {
  SomeViewController().setupSearchBar()
}
2 Likes