DEV Community

DispatchQ
DispatchQ

Posted on

Do away with weak, unowned self-access if you're not mutating self in itself.

Do away with weak, unowned self access if you're not mutating self in itself. Capture interested properties of self in the closure.

Example:

// 1
let closure = { [weak self] in
  self?.navigationItem.rightBarButtonItems = [...]
}

or 

// 2
let anotherClosure = { [weak self] in
    let identifier = self?.identifier ?? "" // identifier is non-optional in SELF scope.
    let request = APIRequest(.../identifier)
  request.perform(...) { _ in

  }
}

In snippet 1 - the self is optionally chained. In snippet 2 - identifier is not optional in the normal self scope.

But just because the self is marked weak it's properties are also needed to chained optionally and results in many optional checks.

Another clean approach is just capture the interested properties of self in closures.

The snippet 1 could be re-written as

let closure = { [navItem = navigationItem] in // could just capture as `[navigationItem] in` also
        navItem.rightBarButtonItems = nil // no self access and no ugly optional checks
}

most importantly accessing it through the local scoped navItem - you'll always get the initial state of the property you're capturing. Attached a Swift playground demoing this powerful feature. Check out!

and the snippet 2 could be written as

let anotherClosure = { [identifier] in
let request = APIRequest(.../identifier)
request.perform(...) { _ in ... }
}

Note: If you really need to mutate self then neccessay closure scoping (weak, unowned) is needed. This example shows clean way to act on properties of self .

Another tip when calling functions of self from inside the closures is to keep a local copy of self before the closure.

func doSomethingWithSelf() {
    let localSelf = self

        let closure = { [localSelf] in
        ...
    }
}

This way the localSelf over the closure is destroyed when the function returns control.

You can also send self as an argument to the closure. This way no capturing/copying is needed. You still can act on self.

func perform<T: AnyObject>(_ reference: T, closure: @escaping (T) -> Void) {
    { [weak reference] in
        guard let strongReference = reference else { return }
        closure(strongReference)
    }()
}

and at the call site no weak self is needed since everything is handled inside the function and the weak, strong casting is done in one place only.

At the call site this looks like:

perform(self) { (reference) in
    reference.doSometing()
}

No more weak self dancing and un-neccessary nil checks. 🎉

Top comments (0)