I have problems with a memory leak when sending ob...
# kotlin-native
d
I have problems with a memory leak when sending objects from a kotlin framework to an objective c class. Basically, once the object has been sent to objective c it is never released. Have I done anything stupid or is this a bug? I will put the code in the thread, but the full project is available here: https://github.com/dtornqvist/LeakingProblem
KotlinNativeFramework.kt:
Copy code
class KotlinNativeFramework {
    var listener: KotlinNativeFrameworkListener? = null

    fun giveMeDoubles() {
        while (true) {
            val myList = DoubleArray(100) { 1.0 }.toList()
            listener?.listenToDoubles(myList) // If this line is commented, the leak stops.
        }
    }
}

interface KotlinNativeFrameworkListener {
    fun listenToDoubles(list: List<Double>)
}
LeakingClass.m:
Copy code
#import "LeakingClass.h"

@interface LeakingClass()

@property (nonatomic, strong) KNFKotlinNativeFramework *knf;

@end


@implementation LeakingClass

-(void)start
{
    _knf = [KNFKotlinNativeFramework new];
    self.knf.listener = self;
    [self.knf giveMeDoubles];
}

- (void)listenToDoublesList:(NSArray<KNFDouble *> *)list {}

@end
s
self.knf.listener = self
Isn’t it a retain cycle?
d
Maybe it is, but the main problem is that each list of doubles is leaking.
Memory usage goes to infinity quite rapidly. If I comment the call to the listener, it is stable.
s
This is highly likely caused by not having autorelease pool for
listenToDoubles
. When calling from Kotlin to Objective-C, Kotlin runtime creates temp Objective-C
NSArray
wrapper for Kotlin
List
.
So your code should look like
Copy code
fun giveMeDoubles() {
        while (true) {
            val myList = DoubleArray(100) { 1.0 }.toList()
            autoreleasepool {
                listener?.listenToDoubles(myList) // If this line is commented, the leak stops.
            }
        }
    }
d
Thanks, that works! How to avoid the retain cycle you commented on? Is it possible to have weak references in kotlin native?
s
d
Thanks!
s
You are welcome!
d
@Svyatoslav Kuzmich [JB] It seems that running the autoreleasepool lambda is quite slow. My kotlin class is event-driven and does all work on a Worker. As events is coming in at a rate of 25-50Hz it is not possible to use the autoreleasepool-lambda at every callback to the listener. I have then manually created my pool when creating the Worker with
pool = objc_autoreleasePoolPush()
and then I periodically call
objc_autoreleasePoolPop(pool)
. Is this best practice or is there another way?
o
@svyatoslav.scherbina ^^^
s
Do I understand correctly that you create your pool only once?
d
Yes
s
This doesn’t look correct to me.
As far as I know, every Objective-C autorelease pool must be popped only once.
d
Ok, it doesn’t seem to crash and I don’t see the memory explosion that I had before. So, the best way perhaps is to avoid sending lists to objective-c? If the conversion between lists and NSArrays is the problem…
s
If I’m correct, then the proper way would be to replace the entire pool periodically, i.e.
Copy code
objc_autoreleasePoolPop(pool)
pool = objc_autoreleasePoolPush()
d
Ok, I see your point! It is nice when this is done for you… 🙂
s
So, the best way perhaps is to avoid sending lists to objective-c? If the conversion between lists and NSArrays is the problem…
No. Normal Objective-C code uses autorelease pools too. So the necessity to release the pool periodically doesn’t seem to be specific for Kotlin/Native
d
Ok, thanks! Not sure why the normal ARC doesn’t take care of it though…
s
Btw, you still can use
autoreleasepool {}
without falling back to its implementation details `objc_autoreleasePoolPop`/`objc_autoreleasePoolPush` with something like this:
Copy code
while (true) {
            autoreleasepool {
                repeat(autoreleaseFrequency) {
                    val myList = DoubleArray(100) { 1.0 }.toList()
                    listener?.listenToDoubles(myList)
                }
            }
        }
d
Thanks, but in the real code my class i called with data and I call
Worker.execute
to perform work and do the callback. It is then not possible to wrap several event-cycles in one lambda.