https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
m

Miguel Schulz

03/17/2023, 10:48 AM
Hello everyone 👀 In Swift, we can use
weak
to avoid reference cycles. Without having Class2.class1 as weak in the following example, this would be a reference cycle and setting Entry.class1 to nil would not release Class1 because it is still hold in a strong reference by Class2, which is hold by Class1 and so on.
Copy code
class Entry {
  private var class1: Class1?
  
  func start() {
    class1 = Class1()
    class1!.start()
  }

  func end() {
    class1 = nil
  }
}

class Class1 {
  lazy var class2 = Class2(class1: self)

  func start() {
    print(class2)
  }
}

class Class2 {
  weak var class1: Class1?
}
Now lets define Class2 not in Swift, but in Kotlin, and it holds a reference to Class1 that implements Class1Protocol. Kotlin:
Copy code
protocol Class1Protocol {
  fun start()
}

class Class2(var class1: Class1Protocol)
Swift:
Copy code
class Class1: Class1Protocol {
  lazy var class2 = Class2(class1: self)

  func start() {
    print(class2)
  }
}
This leads to the reference cycle described above. How can we make sure that Class1 and Class2 get released properly when Entry.end() is called? We know about using expect/actual WeakReference, but this does not seem optimal. Are there any other approaches you can recommend? CC @Paul Woitaschek @Sebastian Muggelberg
c

CLOVIS

03/17/2023, 11:00 AM
Do you mean regular Kotlin references, or a native pointer? Regular Kotlin variables are garbage-collected, the garbage collector is smart enough to find dependency circles automatically.
p

Paul Woitaschek

03/17/2023, 11:35 AM
He means that the swift object doesn’t get it’s deinit called I think
Copy code
class NoKotlin {
        init(){
            print("I'm NoKotlin")
        }
        deinit{
            print("Bye NoKotlin")
        }
    }
    
    class YesKotlin {
        var ref : KotlinRef? = nil
        init(){
            self.ref = KotlinRef(instance: self)
            print("I'm YesKotlin. I never say bye!")
        }
        deinit{
            print("Bye YesKotlin")
        }
    }
    func run() {
        NoKotlin()
        YesKotlin()
    }
I think this demonstrates it quite well with the kotlin part being:
Copy code
public class KotlinRef(public val instance: Any)
Copy code
I'm NoKotlin
Bye NoKotlin
I'm YesKotlin. I never say bye!
The root problem is that we have some swift ui views which subscribe to kotlin flows, but when the user leaves the views, these flows continue to emit
c

CLOVIS

03/17/2023, 12:20 PM
Cold flows cannot emit by themselves
m

Miguel Schulz

03/17/2023, 12:21 PM
The flow is only a symptom. The root problem is that in the example that @Paul Woitaschek send, the Bye YesKotlin is never printed because the deinit is never called.
j

Jeff Lockhart

03/17/2023, 1:24 PM
The Kotlin/Native garbage collector won't run immediately on reference release as ARC does in Swift and ObjC. It runs periodically as needed.
m

Miguel Schulz

03/17/2023, 1:30 PM
Can you specify “periodically”? Because I have seen instances being held alive for more than 30 minutes
Is the instance released after manually performing GC with
kotlin.native.internal.GC.collect()
after releasing the Kotlin reference?
3 Views