Capturing NWConnection in Receive Closure – Risk of Strong Reference Cycle?

Hi Everyone,

I have a query regarding capturing an NWConnection instance inside the receive closure, which gets invoked whenever some raw bytes are received. I want to know whether this will create a strong retain cycle or not.

My understanding is that NWConnection holds a reference to the closure, and if I capture the NWConnection instance inside the closure, the closure will have a reference back to the connection, which, according to my understanding, creates a strong reference cycle.

Is my understanding correct? If so, how can we break the strong reference cycle — using a capture list, or is there any other way as well?

Thanks

Answered by DTS Engineer in 861546022

My understanding is that NWConnection holds a reference to the closure, and if I capture the NWConnection instance inside the closure, the closure will have a reference back to the connection, which, according to my understanding, creates a strong reference cycle.

Is my understanding correct?

Sort of.

You're right that it would be holding a strong reference; however, the key word in the documentation for receive(minimumIncompleteLength..) and receiveMessage(completion:) is "single":

"Schedules a single ... completion handler"

In both cases, what NWConnection is actually doing is:

  1. Retaining the block for later use.

  2. Calling the block when the data is received.

  3. Releasing the block after it's been called once.

That leads to here:

If so, how can we break the strong reference cycle — using a capture list, or is there any other way as well?

Strictly speaking, I believe there's already a retain cycle here as I don't think NWConnection will destroy itself as long as there is a "pending" receive block. However, NWConnection is also what's breaking that cycle by releasing the block after it's been called. FYI, this snippet from "Building a custom peer-to-peer protocol" is what this actually looks like "in practice":

	func receiveNextMessage() {
		guard let connection = connection else {
			return
		}

		connection.receiveMessage { (content, context, isComplete, error) in
			// Extract your message type from the received context.
			if let gameMessage = context?.protocolMetadata(definition: GameProtocol.definition) as? NWProtocolFramer.Message {
				self.delegate?.receivedMessage(content: content, message: gameMessage)
			}
			if error == nil {
				// Continue to receive more messages until you receive an error.
				self.receiveNextMessage()
			}
		}
	}

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

My understanding is that NWConnection holds a reference to the closure, and if I capture the NWConnection instance inside the closure, the closure will have a reference back to the connection, which, according to my understanding, creates a strong reference cycle.

Is my understanding correct?

Sort of.

You're right that it would be holding a strong reference; however, the key word in the documentation for receive(minimumIncompleteLength..) and receiveMessage(completion:) is "single":

"Schedules a single ... completion handler"

In both cases, what NWConnection is actually doing is:

  1. Retaining the block for later use.

  2. Calling the block when the data is received.

  3. Releasing the block after it's been called once.

That leads to here:

If so, how can we break the strong reference cycle — using a capture list, or is there any other way as well?

Strictly speaking, I believe there's already a retain cycle here as I don't think NWConnection will destroy itself as long as there is a "pending" receive block. However, NWConnection is also what's breaking that cycle by releasing the block after it's been called. FYI, this snippet from "Building a custom peer-to-peer protocol" is what this actually looks like "in practice":

	func receiveNextMessage() {
		guard let connection = connection else {
			return
		}

		connection.receiveMessage { (content, context, isComplete, error) in
			// Extract your message type from the received context.
			if let gameMessage = context?.protocolMetadata(definition: GameProtocol.definition) as? NWProtocolFramer.Message {
				self.delegate?.receivedMessage(content: content, message: gameMessage)
			}
			if error == nil {
				// Continue to receive more messages until you receive an error.
				self.receiveNextMessage()
			}
		}
	}

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Capturing NWConnection in Receive Closure – Risk of Strong Reference Cycle?
 
 
Q