Is Self Captured Within a Nested Function

Is self captured within a nested function

Unfortunately, only Closures have "Capture List" feature like [weak self]. For nested functions, You have to use normal weak or unowned variables.

func myInstanceMethod() {
weak var _self = self
func nestedFunction(result : Bool) {
_self?.anotherInstanceMethod()
}

functionExpectingClosure(nestedFunction)
}

Am I capturing self in this nested function? The compiler does not fire a warning

When you assign a closure to tableViewHandler.didSelectRow, you assign to it and retain whatever that closure captures.

self is retaining tableViewHandler.

Therefore, the danger is that you will refer to self within the closure. If you do, that's a retain cycle.

And this might not be due to referring to self explicitly. Any mention of a property or method of self is an implicit reference to self.


Okay, so with that out of the way, let's examine the closure.

You do not mention self implicitly or explicitly in the body of the closure. However, you do call a local method, categorySelected. Therefore, you capture this method.

And categorySelected does mention self. Therefore, it captures self (because every function is a closure).

Thus there is a potential retain cycle and you should continue to say unowned self to prevent a retain cycle.

(I presume that the compiler can't help you here by warning of the retain cycle; there's too much complexity. It's a problem you just have to solve by human reason.)

Why does a nested self-capturing function interfere with isKnownUniquelyReferenced(_:)?

Swift 3.1 Update

As of Swift 3.1, available with Xcode 8.3 beta, this has been fixed. self is no longer boxed at all in the method, therefore isKnownUniquelyReferenced(_:) returns true as expected.


Pre Swift 3.1

I consider this a bug, and have filed a bug report (SR-3530). I was however interested by the cause of this problem, so did some digging – and this is what I found.

Taking a look at the canonical SIL generated for the bar() method (for an -Onone build) reveals that Swift is heap allocating a box (alloc_box) for self at the very beginning of the method – so that it can be captured by nestedFunc().

// Bar.bar() -> ()
sil hidden @main.Bar.bar () -> () : $@convention(method) (@inout Bar) -> () {
// %0 // users: %10, %3
bb0(%0 : $*Bar):

// create new heap-allocated box, and store self in it.
// this is where the problem stems from – there are now two copies of the Bar instance, thus isKnownUniquelyReferenced will return false.
%1 = alloc_box $Bar, var, name "self", argno 1, loc "main.swift":15:26, scope 9 // users: %11, %9, %7, %2
%2 = project_box %1 : $@box Bar, loc "main.swift":15:26, scope 9 // users: %10, %5, %3
copy_addr %0 to [initialization] %2 : $*Bar, scope 9 // id: %3

// call isKnownUniquelyReferenced (I removed the print() function call as it generates a bunch of unrelated SIL).
// function_ref isKnownUniquelyReferenced<A where ...> (inout A) -> Bool
%4 = function_ref @Swift.isKnownUniquelyReferenced <A where A: Swift.AnyObject> (inout A) -> Swift.Bool : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:9, scope 10 // user: %6
%5 = struct_element_addr %2 : $*Bar, #Bar.foo, loc "main.swift":17:35, scope 10 // user: %6
%6 = apply %4<Foo>(%5) : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:39, scope 10

// retain the heap-allocated box containing self, in preparation for applying nestedFunc() with it.
// (as it's passed as an @owned parameter).
strong_retain %1 : $@box Bar, loc "main.swift":27:9, scope 10 // id: %7

// call the nested function with the box as the argument.
// function_ref Bar.(bar() -> ()).(nestedFunc #1)() -> ()
%8 = function_ref @main.Bar.(bar () -> ()).(nestedFunc #1) () -> () : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:9, scope 10 // user: %9
%9 = apply %8(%1) : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:20, scope 10

// once called, copy the contents of the box back to the address of the Bar instance that was passed into the method, and release the box.
copy_addr %2 to %0 : $*Bar, scope 10 // id: %10
strong_release %1 : $@box Bar, loc "main.swift":29:5, scope 10 // id: %11

// so cute.
%12 = tuple (), loc "main.swift":29:5, scope 10 // user: %13
return %12 : $(), loc "main.swift":29:5, scope 10 // id: %13
}

(Full SIL here)

Because of this boxing, there are now two copies of the Bar instance in the bar() method, therefore meaning that isKnownUniquelyReferenced(_:) will return false, as there are two references to the Foo instance.

From what I can tell, the boxing of self at the beginning of the method seems to be a consequence of the optimisation of the mutating method from copy-in copy-out (self gets boxed at the start of the method call, mutations are then applied to that box, then written back to the callee at the end of the method) to pass-by-reference (this optimisation occurs between the raw SIL and canonical SIL).

The same box that was used in order to create a copy of self to mutate within the method is now used in order to capture self to call the nested function with. I see no reason why the box for the capture shouldn’t be created just before the call to nestedFunc(), as that’s the logical place to capture self (rather than at the beginning of the method).

Although, in any case, the creation of a box is completely redundant in the first place, as nestedFunc() doesn’t and cannot escape. Attempting to return nestedFunc() from the method yields the following compiler error:

Nested function cannot capture inout parameter and escape

So it really just looks like a corner case that hasn’t been optimised yet. Even in an -O build, although the heap allocation for the Bar instance is able to be optimised to a stack allocation for just the foo property, this still results in an unnecessary second reference to the Foo instance.


Solutions

One solution would be to just add an inout self parameter to nestedFunc(), allowing self to just be passed by reference, rather than being captured:

func nestedFunc(_ `self`: inout Bar) {
_ = self // do something useful with self
}
// ...
nestedFunc(&self)

which now generates the SIL (-Onone):

// function_ref Bar.(bar() -> ()).(nestedFunc #1)(inout Bar) -> ()
%5 = function_ref @main.Bar.(bar () -> ()).(nestedFunc #1) (inout main.Bar) -> () : $@convention(thin) (@inout Bar) -> (), loc "main.swift":31:9, scope 10 // user: %6
%6 = apply %5(%0) : $@convention(thin) (@inout Bar) -> (), loc "main.swift":31:25, scope 10

The advantage of this solution is that it's just a straightforward pass-by-reference (as the Bar parameter is marked @inout). Because of this, there only ever exists one copy of the instance of Bar – thus isKnownUniquelyReferenced(_:) can return true.

Another possible solution, if self isn’t mutated within nestedFunc(), is to pass self by value, rather than reference. This can be done with a capture list in a local closure:

let nestedFunc = { [`self` = self] in // copy self into the closure.
_ = self // the self inside the closure is immutable.
}
// ...
nestedFunc()

The advantage is that you don’t need to explicitly pass anything into the nestedFunc() call. Because the instance of Bar isn’t passed by value until the closure creation – it won’t interfere with the call to isKnownUniquelyReferenced(_:), assuming that the call precedes the closure creation.

Function inside Function retain cycle

Just assign your closure to a local variable. At that point, extracting out dismiss is pointless, so just inline it:

private func setupDismissCallbacks() {     
let dismissCallback: () -> Void = { [weak self] in
guard let self = self else { return }
self.videoExporter?.cancel()
self.rootViewController.dismiss(animated: true, completion: nil)
self.delegate?.childCoordinatorDidFinish(self)
}

saveModalViewController.onButtonDismiss = dismissCallback
saveModalViewController.onDimmedAreaDismiss = dismissCallback
}

python: is it legal to pass self to the nested function inside the class method?

Any function defined within a scope can use variables from its enclosing scope. In this case, you're defining a function within a function, so all the variables in the enclosing function are available - self, param1, and param2. So you can pass self as a parameter to the inner function, but since it already has access to self, there's not much point to doing so.

If you create new variables with these names inside your new function, they "shadow" the variables with those names in the outer scope, meaning the names from the outer scope are inaccessible as long as the same names refer to something else in the inner scope. self is just a normal variable name with no special meaning, except that it's the first parameter of a class's instance function, and thus gets passed implicitly when the method is called via the dot operator - meaning that it's subject to these rules.

In your inner function, you're passing self explicitly, which is harmless but also pointless in this case. Python 3.6 (and Pycharm) is giving you warnings because it can't do typechecking on this, but that's all.

In other words, this would work just as well and cause no errors:

def func1(self, param1, param2):
def inner_func1():
print(self, self.a, self.b)

inner_func1()

Use [weak self] in the nested block in swift

If you don't use [weak self], then for the life time of that block, you can have a circular reference between it and the object self refers to, when an object loses a reference to it, its reference drops by one, when it reaches zero then it is deallocates and reduces the reference count of any object it has a reference to. If you have a circular reference then neither are going to reach zero, because neither is going to get to zero to deallocate itself and reduce the reference to the other. For regular objects this is a problem because they will never be deallocated, for block though, it can depend on how they are used, if they are passed to a function that uses them straight away, then once they are executed they will be deallocated, and any circular references will be cut, it may be even beneficial that whilst your block is executing that it and self can't go away, but if the block is retained as an instance variable to be called, then you have a circular reference that will never go away. The way to deal with is is to use [weak self], saying references to self are weak in this block, you can then just deal with that on each time you use it, self?.myFunction() for example, or you can create a strong reference at the beginning, it used to be you used to have to use a different variable to self, but now you can go

guard let self = self else { return }

one of the good things of doing it this way, you are saying if it gets this far I want the block to execute completely, you have created a circular reference for the execution of the block and it will not go away until the block finishes executing. For example if you have a few functions starting with self?, then mid way through your block executing self could become nil, and only the first few functions are executed and the later are not.

Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?

The [weak self] in anotherFunctionWithTrailingClosure is not needed.

You can empirically test this:

class Experiment {
func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}

func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}

func doSomething() {
print(#function)
}

func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}

// go ahead and add `deinit`, so I can see when this is deallocated

deinit {
print("deinit")
}
}

And then:

func performExperiment() {
DispatchQueue.global().async {
let obj = Experiment()

obj.testCompletionHandlers()

// sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler

Thread.sleep(forTimeInterval: 1.5)
}
}

If you do this, you will see that doSomething is never called and that deinit is called before anotherFunctionWithTrailingClosure calls its closure.

That having been said, I might still be inclined to use the [weak self] syntax on anotherFunctionWithTrailingClosure to make my intent explicit.

How to perform selector with a nested function?

instead, you can try below code to achieve your goal

class MyClass {
let button = UIButton()

@objc public func parentFunc(_ sender : UIButton?)
{

func childFunc() {
print("User tapped")
}

if sender != nil && sender.tag == 100{
childFunc()
return
}

button.addTarget(self, action: #selector(parentFunc(_:)), for: .touchUpInside)
button.tag = 100
}
}

In above code, Sender is optional so you can pass nil when you don't want to call child function like parentFunc(nil)



Related Topics



Leave a reply



Submit