r

    rb90

    1 year ago
    Hi, I have a question on kotlin multiplatform and memory management for ios and I am very grateful if someone has an hint for me here. When using classes from a kotlin multiplatform shared module in an ios app (f.e. in a local function context in a view controller) - when this view controller gets removed from memory the kotlin multiplatform sdk classes still continue living in the memory. Is there a way to also remove them from there? Is that a known issue?
    Martins Iroka

    Martins Iroka

    1 year ago
    Hello, were you able to find an answer to this?
    louiscad

    louiscad

    1 year ago
    This is normal behavior so long only the classes and top-level objects stay there, and unused instances are eventually garbage collected.
    r

    rb90

    1 year ago
    Thank you for the response here 🙂. The current situation is that the ios classes requesting kotlin code were already removed from the memory as expected (f.e. when view controller closed). But the kotlin classes used by the removed swift classes are still there in the memory. It looks like unused instances who still live somewhere in the memory. And the caller is not there anymore. But if you say that this is normal behavior and the garbage collector remove them automatically after some time then this is probably the answer 🙂.
    louiscad

    louiscad

    1 year ago
    You can check that by allocating a few large arrays (e.g. of size 1M), and not reference them after creating them. They should not stay in memory.
    r

    rb90

    1 year ago
    Thank you for your response @louiscad So I tried what you proposed, I created an array of kotlin multiplatform sdk classes in a local context of a function:
    func testFunc() {
            let testArray = [UcLoginRequests(), UcLoginRequests(), UcLoginRequests(), UcLoginRequests(), UcSignupRequests(), UcSignupRequests(), UcSignupRequests(), UcSignupRequests(), UcSignupRequests()]
        }
    That function is inside a viewModel. It is called from a viewController. After that viewController is closed and removed from stack, also the associated viewModel is gone from the memory. But unfortunately this classes are still in the memory:
    This is probably not the expected behavior you described right? This references should not be in the memory after the viewController and viewModel is removed from memory right?
    louiscad

    louiscad

    1 year ago
    @rb90 If I understand correctly, you call that
    testFunc
    Swift function that creates an array out of Kotlin objects, and the Kotlin objects stay in memory long after? How long after do you check the memory contents? Also, do you have the debugger connected all that time? I know it can delay GC on Android, I don't know if the same is true for iOS with Kotlin/Native.
    r

    rb90

    1 year ago
    If I understand correctly, you call that 
    testFunc
     Swift function that creates an array out of Kotlin objects, and the Kotlin objects stay in memory long after?
    that's right 👍 I check the memory by debugging the memory graph in xcode. I started that 1-2 minutes after the viewModel and the viewController calling that function are gone. And the elements are still present in the memory as you can see 😞. I would expect them to not be removed from memory like the viewController and the viewModel. I cannot understand why they are not garbage collected when the parent classes are not there anymore
    louiscad

    louiscad

    1 year ago
    @rb90 Can you reproduce the same issue with a custom type that holds no reference to anything? Somthing like that:
    class WIllILeak(val justSomeRandomText: String)
    r

    rb90

    1 year ago
    Aaaah interesting - I init a "data class" from the kotlin sdk in the my test function and with this I cannot reproduce the issue: EDIT: It is reproducable with the data class ErrorObject. Unfortunately a call in the view Controller was not triggered that was why I thought that the data class does not leak 😞:
    func testFunc() {
            let testArray = [ErrorObject(errorMessage: "hello", errorStacktrace: "a stacktrace", errorType: .dataMissingInResponseError, statusCode: nil), ErrorObject(errorMessage: "hello", errorStacktrace: "a stacktrace", errorType: .dataMissingInResponseError, statusCode: nil), ErrorObject(errorMessage: "hello", errorStacktrace: "a stacktrace", errorType: .dataMissingInResponseError, statusCode: nil), ErrorObject(errorMessage: "hello", errorStacktrace: "a stacktrace", errorType: .dataMissingInResponseError, statusCode: nil), ErrorObject(errorMessage: "hello", errorStacktrace: "a stacktrace", errorType: .dataMissingInResponseError, statusCode: nil)]
        }
    ErrorObject
    is a simple data class.... I also add now a simple class into my module and will try that - it takes a few minutes the build 😁 - I do not use the mono repo structure...
    But really what I can say in the meantime is - I really love kotlin multiplatform. It is very flexible and a nice opportunity I hope that you guys get a "release release" soon. I believe if it works as intended it will be a game changer. And also a big thank you here for the support. It is also nice to look together on something if it does not behave and work like intended....
    It is 100% reproducible with a TestClass looking like you wrote. My Kotlin TestClass:
    class TestClass(val justSomeRandomText: String) { }
    my test function in the viewModel:
    func testFunc() {
            let test = TestClass(justSomeRandomText: "bla")
            let testArray = [TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla"), TestClass(justSomeRandomText: "bla")]
        }
    And same behaviour, viewModel and viewController removed from the memory but the kotlin class instances still there:
    @louiscad unfortunately it leaks also with the data class I was wrong that it doesn't. I updated my post above regarding that problem. So seems that kotlin multiplatform cause memory leaks on ios
    louiscad

    louiscad

    1 year ago
    Interesting… Do you see the same issue in a brand new project? Also, which Kotlin version is your project using?
    r

    rb90

    1 year ago
    I used the newest kotlin and android studio. I did not tried it in a brand new project until now 😕. I will give it a try 🙂
    louiscad

    louiscad

    1 year ago
    @rb90 Writing he "newest kotlin" will never age well. I'm asking about the Kotlin version configured in the Gradle files, not the IDE version. In the Kotlin world, the IDE is "only" a tool to view the project, it's not the build tool itself like you might be used to with XCode.
    r

    rb90

    1 year ago
    Ah yes sorry 😉 - I am a kotlin beginner 😕. The kotlin version used is 1.4.30
    louiscad

    louiscad

    1 year ago
    Can you try with the freshly released 1.5.0?
    r

    rb90

    1 year ago
    Of course - I will try it 🎉 👍 - thanks for the hint 👍
    louiscad

    louiscad

    1 year ago
    Another question: the Swift code allocation the array, is it called from the main thread, or is it called from somewhere else like a non main thread dispatch queue?
    r

    rb90

    1 year ago
    This is called from an IBAction - so main thread 🙂.
    @IBAction func myAction(_ sender: Any) {
          viewModel?.testFunc()
    }
    louiscad

    louiscad

    1 year ago
    If the problem still reproduces with Kotlin 1.5, can you try generating a way larger list/array (so not manually, but using some code to give a size an initialize each element with a new object), with at least 100 000 items in the array? I'm asking because I think the Kotlin GC has a memory threshold where it doesn't come cleaning objects below a certain memory usage from the Kotlin/Native world
    In Kotlin, it'd look like
    List(100_000) { WillILeak("Hello " + Random.nextInt()) }
    r

    rb90

    1 year ago
    Yeah will do that 👍 - of course anything what help 🙂 I do my best
    Oh no - it still happens with Kotlin version 1.5.0 😞. My test function:
    func testFunction() {
            var array = [TestClass]()
            
            for index in 0..<100000 {
                array.append(TestClass(justSomeRandomText: "abc"))
            }
        }
    Memory graph:
    hmmm.... okay I tried it now in the monorepoproject. I used the framework packed inside "xcode-frameworks" folder. There it does not happen anymore. It looks normal and as it should be. Normally I build the framework file with the multiplatform-swiftpackage gradle plugin. Maybe the way this plugin generates classes is the reason for that problems. The generated classes may cause this behaviour....
    I guess I got the reason.
    multiplatform-swiftpackage
    packs an
    *.xcframework
    file. I did that a few minutes ago on my own with xcodebuild command without
    multiplatform-swiftpackage
    . And the result is the same. The classes are causing leaks in a project where the generated framework is added (added manually the
    *.xcframework
    without swift package manager and
    multiplatform-swiftpackage
    ).... It seems that this problem occurs when the kotlin classes are packed in an
    *.xcframework
    😞. Then this leaks happen 😢.
    This was unfortunately not the reason. Same behaviour compiling the project:
    - ./gradlew clean
    - ./gradlew build
    - ./gradlew packForXcode
    Putting that
    *.framework
    in an app (like f.e. described here in the docs https://kotlinlang.org/docs/mobile/integrate-in-existing-app.html#create-a-shared-module-for-cross-platform-code) cause exactly same issues - so the
    *.xcframework
    file seems not to be the problem when
    *.framework
    behaves exactly the same 😞
    Edited my post above - I forget to click the button calling the
    testFunc()
    when playing around with the monorepo project. In the monorepo project the bahviour is exactly the same - there are memory leaks:
    What happens in that test project in the mono repo  (the same like in my main project): • We have a ViewController "FirstScreen" • Action "Go to next screen" opens "NextViewController" (via a 
    present segue
     ) • NextViewController has a viewModel dependency • this viewModel dependency has this test func:
    func testFunc() {
            var array = [TestClass]()
            for index in 0..<100000 {
                array.append(TestClass(justSomeRandomText: "abc"))
            }
        }
    • SecondViewController has an action ("My Action") • inside @IBAction method 
    viewModel?.testFunc()
    is triggered • after that close Button is clicked which dismisses "NextViewController" -> when this vc and the viewModel is gone I would expect that also the memory is cleanup and there are no references anymore of the instantiated array containing 
    TestClass
     objects:
    louiscad

    louiscad

    1 year ago
    @svyatoslav.scherbina Did you ever see such a leak in Swift<->Kotlin interop?
    s

    svyatoslav.scherbina

    1 year ago
    Kotlin/Native doesn’t reclaim the memory immediately. Try
    kotlin.native.internal.GC.collect()
    to force unused memory to be reclaimed.
    r

    rb90

    1 year ago
    @svyatoslav.scherbina Thank you for your reply on this and the information regarding the memory handling. I would like to try your approach. Can you please tell me how to call that exactly? Where should I make that call? In the Swift Code? Or would I need to create a method in the jolting Multiplattform with that line and call this function then somewhere in my Swift code?
    louiscad

    louiscad

    1 year ago
    @rb90 It's not visible to Swift/Obj-C, so you'll need a Kotlin function that does it than you can call from Swift/Obj-C
    r

    rb90

    1 year ago
    Aaah okay, nice 👍, that is good to know. In that case I will put it in a helper kotlin class and call it from Swift like you suggest. I will let you know here how it goes guys. Thank you so much for the support.
    AMAZING it is working with that magic line. The objects in the memory disappeared magically 👍. Thank you so much. But also without this line they would be probably removed after some time right? From what I understand from Svyatoslav answer 🙂.
    louiscad

    louiscad

    1 year ago
    I guess they would be removed after some memory pressure, more than after some time, but I don't know the internals of the current GC in Kotlin/Native that much. I attended a talk from Nikolay Igotti (now working on Compose dekstop stuff AFAIK) on the topic in march 2020, and shared with him the last handshake of the year after it, but I understood about a quarter of it 😅
    r

    rb90

    1 year ago
    Ah okay 🙂 - anyways as long as we can manage memory ourselfs if necessary with that line - it is fine. I was just curious that is why I asked 😉.
    s

    svyatoslav.scherbina

    1 year ago
    I guess they would be removed after some memory pressure
    Yes.
    r

    rb90

    1 year ago
    Nice stuff 🙌 - thank you very much for your reply on this Svyatoslav