Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent strong reference cycles when using Apple's new Combine framework (.assign is causing problems)

Tags:

I don't quite understand how to properly store subscribers inside a class so that they persist but don't prevent the object from being deinitialized. Here's an example where the object won't deinit:

import UIKit import Combine  class Test {     public var name: String = ""      private var disposeBag: Set<AnyCancellable> = Set()      deinit {         print("deinit")     }      init(publisher: CurrentValueSubject<String, Never>) {         publisher.assign(to: \.name, on: self).store(in: &disposeBag)     } }  let publisher = CurrentValueSubject<String, Never>("Test")  var test: Test? = Test(publisher: publisher) test = nil  

When I replace the assign with a sink (in which I properly declare [weak self]) it actually does deinit properly (probably because the assign accesses self in a way that causes problems).

How can I prevent strong reference cycles when using .assign for instance?

Thanks

like image 713
Quantm Avatar asked Sep 17 '19 18:09

Quantm


People also ask

How can you avoid strong reference cycles in a closure Swift?

A strong reference cycle happens when 2 instances keep a strong reference to each other. You can accidentally create such a cyclic reference, for example when working with 2-way “links” between objects, or with closures. You can break the cycle by marking a reference as weak, or by setting one of the references to nil.

What is combine framework IOS?

The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.


1 Answers

you can replace .asign(to:) with sink where [weak self] in its closure brake the memory cycle. Try it in Playground to see the difference

final class Bar: ObservableObject {     @Published var input: String = ""     @Published var output: String = ""      private var subscription: AnyCancellable?      init() {         subscription = $input             .filter { $0.count > 0 }             .map { "\($0) World!" }             //.assignNoRetain(to: \.output, on: self)             .sink { [weak self] (value) in                 self?.output = value         }      }      deinit {         subscription?.cancel()         print("\(self): \(#function)")     } }  // test it!! var bar: Bar? = Bar() let foo = bar?.$output.sink { print($0) } bar?.input = "Hello" bar?.input = "Goodby," bar = nil 

it prints

Hello World! Goodby, World! __lldb_expr_4.Bar: deinit 

so we don't have the memory leak !

finally at forums.swift.org someone make a nice little

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     }   } } 
like image 147
user3441734 Avatar answered Oct 08 '22 01:10

user3441734