Assign operator can cause a strong reference cycle

assign(to: \.word, on: self) and storing the resulting AnyCancellable results in a strong reference cycle. Replacing assign(to:on:) with assign(to: &$word) prevents this problem

not sure why

@rpay_ios You create a strong reference cycle with assign(to:on:) in Combine like this:

class Person {
  var subscriptions = Set<AnyCancellable>()
  @Published private var name: String = ""
  private var fullName = ""
  let nameSubject = PassthroughSubject<String, Never>()
    
  func showFullName(for names: [String]) -> String {
    nameSubject.assign(to: \.name, on: self).store(in: &subscriptions)
    $name.sink{self.fullName.append("\($0) ")}
    for name in names {
      nameSubject.send(name)
    }
    fullName.removeFirst()
    fullName.removeLast()
    return fullName
  }
  
  deinit {
    print("Goodbye \(fullName)!")
  }
}

var me: Person? = Person()
let names = ["Cosmin", "George", "Pupăză"]
let fullName = me!.showFullName(for: names)
print("My full name is \(fullName).")
me = nil

The above block of code only prints My full name is Cosmin George Pupăză. to the console because you create a strong reference cycle in this case when you assign nameSubject to name in the showFullName(for:) method. This means that the Person class deinitializer doesn’t get called when you set me to nil because of that.

You can actually break the strong reference cycle in four different ways in Combine:

  1. Remove all subscriptions when you are finally done with them as follows:
// original code
me!.subscriptions.removeAll()
me = nil
  1. Send a completion event to nameSubject when you are finally done with it like this:
// original code
me!.nameSubject.send(completion: .finished)
me = nil
  1. Define subscriptions as Set<AnyCancellable>? in order to make it a weak property as follows:
class Person {
  var subscriptions: Set<AnyCancellable>? = Set()
  // original code
  
  func showFullName(for names: [String]) -> String? {
    guard var subscriptions = subscriptions else {return nil}
    // original code
  }
  
  // original code
}

var me: Person? = Person()
let names = ["Cosmin", "George", "Pupăză"]
if let fullName = me!.showFullName(for: names) {
  print("My full name is \(fullName).")
} else {
  print("No full name for me!")
}
me = nil
  1. Use assign(to:) in order to connect nameSubject with name in the showFullName(for:) method like this:
func showFullName(for names: [String]) -> String {
  nameSubject.assign(to: &$name)
  // original code
}

All of the above four solutions print Goodbye Cosmin George Pupăză! to the console since there is no strong reference cycle anymore so the Person class deinitializer gets called this time.

Please let me know if you have any questions or issues about the whole thing when you get a chance. Thank you!

thanks for your kind explanation.

but I want to know why the strong reference can be caused.