pushkarnk pushkarnk - 3 months ago 15
Swift Question

Unowned property not captured in async block

I am working with code which is structured like this snippet (this needs Swift 3):

import Dispatch

var processing = false

class Customer {
var card: CreditCard!
var name: String!

init(name: String) {
self.name = name
}

func createCard() -> CreditCard {
card = CreditCard(customer: self)
return card
}

func check() {
}
}

class CreditCard {
unowned let customer: Customer //if not "unowned" a reference cycle will result
let queue = DispatchQueue.global() //needs Swift 3 to compile

init(customer: Customer) {
self.customer = customer
}

private func doBackgroundCheck() {
self.customer.check()
}

func process() {
queue.async {
//self.customer is no captured
self.doBackgroundCheck()
print("processed")
processing = false
}
}
}

func issueCard(to name: String) {
let c = Customer(name: name)
let card = c.createCard()
processing = true
card.process()
}


//main
issueCard(to: "Tom")
while processing {
sleep(1)
}


This code crashes, because the unowned property "customer" is not captured in the async block. The customer object gets deallocated before the async block can run. Making "customer" a strong reference works but it can cause a reference cycle leading to a leak.

I wasn't able to find any guidance on such code patterns in the Apple documentation. Can anyone help please? Thanks!

Answer

You can capture a strong reference to self.customer using a "capture list" in the closure:

func process() {
    queue.async { [customer = self.customer] in
        customer.check()
        print("processed")
        processing = false
    }
}

Inside the closure, customer is a strong reference to self.customer, which exists until the closure has been executed. This causes a temporary retain cycle, but not a permanent one, because the closure is executed eventually.