[Combine] assign(to:on:) causes memory leak

It seems Apple did fix this issue in iOS 14, by introducing the assign(to:) operator, which takes a Published.Publisher as its input and instead of returning an AnyCancellable, it binds the lifetime of the subscription to the lifetime of the Published property.
https://developer.apple.com/documentation/combine/publisher/assign(to:)

What a beautiful day!

I've tried to add a search bar to my Webview player.
That's cool to let any user enter their favorite URL instead of static tap action.

you can download the app here

Here is the view

struct AddressBarView : View {
    @ObservedObject var viewModel: AddressBarViewModel
    
    init(_ vm: AddressBarViewModel) {
        viewModel = vm
    }
    
    var body : some View {
        VStack {
            SearchBar(text: $viewModel.barString, tapSearch: viewModel.tapGo, placeholder: "enter url here".localized)
        }.onAppear(perform: {
            self.viewModel.transform()
        })
    }
}


struct SearchBar: UIViewRepresentable {

    @Binding var text: String
    let tapSearch: PassthroughSubject<Void, Never>
    
    var placeholder: String
    

    class Coordinator: NSObject, UISearchBarDelegate {
        @Binding var text: String
        private let tapSearch: PassthroughSubject<Void, Never>

        init(text: Binding<String>, didSearch: PassthroughSubject<Void, Never>) {
            _text = text
            tapSearch = didSearch
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
        
        func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
            tapSearch.send()
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text, didSearch: tapSearch)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.placeholder = placeholder
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}

And view model

class AddressBarViewModel : ObservableObject, ViewModelType {
    @Published var barString: String = ""
    @Published var goWithURL: URL?
    
    private let tapGo = PassthroughSubject<Void, Never>()
    private var cancellables = Set<AnyCancellable>()
    
    func transform() {
        tapGo.map { [weak self] () -> URL? in
            guard let `self` = self else { return nil }
            return URL(string: self.barString.fixHTTP())
            
        }.filter {$0 != nil}
        .assign(to: \.goWithURL, on: self)
        .store(in: &cancellables)
    }
}

The big problem I found is .assign(to: \.goWithURL, on: self) will cause memory leak, self object will be capture as retained value.

So after several searches, I found that we can use sink instead of assign(to:on:) to capture self as weak value.

tapGo.map { [weak self] () -> URL? in
    guard let `self` = self else { return nil }
    //the fixHTTP method just extension to add http or https when missing
    return URL(string: self.barString.fixHTTP())
}.filter {$0 != nil}
.sink(receiveValue: { [weak self] url in
    self?.goWithURL = url
})
.store(in: &cancellables)

Or an extension will make our code cleaner:

extension Publisher where Self.Failure == Never {
    public func assignNoRetain<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable where Root: AnyObject {
        sink { [weak object] (value) in
            object?[keyPath: keyPath] = value
        }
    }
}

Tada! Now we can use it easily.

tapGo.map { [weak self] () -> URL? in
    guard let `self` = self else { return nil }
    return URL(string: self.barString.fixHTTP())
}.filter {$0 != nil}
.assignNoRetain(to: \.goWithURL, on: self)
.store(in: &cancellables)

I hope Apple support to solve this problem soon.
Best.