How does captured self in a protocol extension work?

If I have a protocol, without a reference type requirement, and in a protocol extension I implement some function with closures that capture self, what happens? Does it depend on whether the type implementing the protocol is a reference of a value type?

I hope this contrived example makes sense:

protocol Networking {
    func doStuff(callback: ()->Void)
    func storeStuff()
}

extension Networking {
    func fetchStuff(callback: ()->Void) {
        URLSession.shared.dataTask(with: URLRequest(url: URL(string: "https://www.google.com")!)) { (_, _, _) in
            self.storeStuff()  // <--------------- compiler enforces adding self here. 
        }.resume()
    }
    func storeStuff() {
        
    }
}

Not that in the example, I couldn't use a capture list to capture self weakly - 'weak' must not be applied to non-class-bound 'Self'; consider adding a protocol conformance that has a class bound.

Just make it class bound

Why would you? For completion handlers it’s correct to use a strong capture. If other references to self are released while the network request is being performed, it’ll just extend the lifetime enough to complete the request. Once the completion handler is done, the reference is released, as it should.

Weak capture is generally used for escaping closures where the closure is stored for multiple future invocations, such as signal/event handlers, etc. For completion handlers, you should be fine.

If you really need a weak capture, bind either your protocol or the extension to classes.

Yeah, it wasn’t a great example. I just reached for the first place I could find a closure. Regardless, though, my question remains.

Even though the answer should be trivial as in the unconstrained case you should expect self to be captured if it's an object and a copy to be made in case of a value type.

Here is some code to experiment with.

import PlaygroundSupport
import Dispatch

let page = PlaygroundPage.current
page.needsIndefiniteExecution = true

struct Weak<T> {
  struct _ObjectBox {
    weak var object: AnyObject?
  }

  enum _Variant {
    case objectBox(_ObjectBox)
    case value(T?)
  }

  var _variant: _Variant

  var value: T? {
    get {
      switch _variant {
      case .objectBox(let box):
        return box.object as! T?
      case .value(let value):
        return value
      }
    }
    set {
      _variant = Self._variant(from: newValue)
    }
  }

  init(_ value: T?) {
    self._variant = Self._variant(from: value)
  }

  static func _variant(from value: T?) -> _Variant {
    switch value {
    case .some(let wrapped) where type(of: wrapped) is AnyObject.Type:
      let box = _ObjectBox(object: wrapped as AnyObject)
      return .objectBox(box)
    case .none where T.self is AnyObject.Type:
      let box = _ObjectBox(object: .none)
      return .objectBox(box)
    default:
      return .value(value)
    }
  }
}

class A {
  deinit {
    print(#function)
  }
}

struct B {}

var a: A? = A()
var b: B? = B()

var weak_a_1 = Weak(a)
var weak_a_2 = Weak<A>(.none)
var weak_b_1 = Weak(b)
var weak_b_2 = Weak<B>(.none)

print(weak_a_1.value as Any) // .some(A)
print(weak_a_2.value as Any) // .none
print(weak_b_1.value as Any) // .some(B)
print(weak_b_2.value as Any) // .none

a = .none
b = .none

// prints "deinit"

print(weak_a_1.value as Any) // .none | released
print(weak_a_2.value as Any) // .none
print(weak_b_1.value as Any) // .some(B) | expected copy
print(weak_b_2.value as Any) // .none

a = A()
b = B()

protocol P {}

extension P {
  var weaklyCaptured: () -> Void {
    let type = Swift.type(of: self)
    let weak = Weak(self)
    return {
      switch weak.value {
      case .some:
        print("\(type) - some")
      case .none:
        print("\(type) - none")
      }
    }
  }
}

extension A: P {}
extension B: P {}

let a_closure = a.map(\.weaklyCaptured)!
let b_closure = b.map(\.weaklyCaptured)!

a_closure() // A - some
b_closure() // B - some

a = .none
b = .none

// prints "deinit"

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
  a_closure() // A - none // released
  b_closure() // B - some

  page.finishExecution()
}