https://kotlinlang.org logo
r

rb90

05/04/2021, 10:33 AM
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?
1
m

Martins Iroka

05/05/2021, 9:03 AM
Hello, were you able to find an answer to this?
l

louiscad

05/05/2021, 9:05 AM
This is normal behavior so long only the classes and top-level objects stay there, and unused instances are eventually garbage collected.
r

rb90

05/05/2021, 9:12 AM
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 🙂.
l

louiscad

05/05/2021, 9:16 AM
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.
👍 1
r

rb90

05/05/2021, 2:47 PM
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:
Copy code
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?
l

louiscad

05/05/2021, 3:02 PM
@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

05/05/2021, 3:09 PM
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
l

louiscad

05/05/2021, 3:11 PM
@rb90 Can you reproduce the same issue with a custom type that holds no reference to anything? Somthing like that:
Copy code
class WIllILeak(val justSomeRandomText: String)
👀 1
r

rb90

05/05/2021, 3:29 PM
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 😞:
Copy code
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:
Copy code
class TestClass(val justSomeRandomText: String) { }
my test function in the viewModel:
Copy code
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
l

louiscad

05/05/2021, 4:55 PM
Interesting… Do you see the same issue in a brand new project? Also, which Kotlin version is your project using?
r

rb90

05/05/2021, 6:24 PM
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 🙂
l

louiscad

05/05/2021, 6:27 PM
@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

05/05/2021, 6:28 PM
Ah yes sorry 😉 - I am a kotlin beginner 😕. The kotlin version used is 1.4.30
l

louiscad

05/05/2021, 6:29 PM
Can you try with the freshly released 1.5.0?
r

rb90

05/05/2021, 6:30 PM
Of course - I will try it 🎉 👍 - thanks for the hint 👍
l

louiscad

05/05/2021, 6:30 PM
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

05/05/2021, 6:32 PM
This is called from an IBAction - so main thread 🙂.
Copy code
@IBAction func myAction(_ sender: Any) {
      viewModel?.testFunc()
}
👍 1
l

louiscad

05/05/2021, 6:35 PM
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

05/05/2021, 6:37 PM
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:
Copy code
func testFunction() {
        var array = [TestClass]()
        
        for index in 0..<100000 {
            array.append(TestClass(justSomeRandomText: "abc"))
        }
    }
Memory graph:
🙏 1
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....
🤔 1
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:
Copy code
- ./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:
Copy code
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:
l

louiscad

05/05/2021, 10:19 PM
@svyatoslav.scherbina Did you ever see such a leak in Swift<->Kotlin interop?
s

svyatoslav.scherbina

05/06/2021, 7:20 AM
Kotlin/Native doesn’t reclaim the memory immediately. Try
kotlin.native.internal.GC.collect()
to force unused memory to be reclaimed.
r

rb90

05/06/2021, 8:27 AM
@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?
l

louiscad

05/06/2021, 8:31 AM
@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

05/06/2021, 8:33 AM
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.
👍 2
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 🙂.
l

louiscad

05/06/2021, 6:24 PM
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 😅
👍 1
r

rb90

05/06/2021, 8:33 PM
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

05/07/2021, 8:40 AM
I guess they would be removed after some memory pressure
Yes.
👍 1
r

rb90

05/07/2021, 1:55 PM
Nice stuff 🙌 - thank you very much for your reply on this Svyatoslav
👍 1
3 Views