iOS 中的内存泄漏 Kotlin 本机库
Memory Leak Kotlin Native library in iOS
我正在使用 Kotlin/Native 构建一个 Kotlin 库以在我的 iOS 应用中使用。在我从 Swift 调用库中的一些方法后,我还想从库中调用 Swift 中的方法。为此,我在库中实现了一个接口:
class Outbound {
interface HostInterfaceForTracking {
fun calcFeatureVector(bitmap: Any?): Array<Array<FloatArray>>?
}
var hostInterface: HostInterfaceForTracking? = null
fun registerInterface(hostInterface: HostInterfaceForTracking) {
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
}
}
这是在 Swift 端实现的,如下所示:
class HostInterfaceForTracking : OutboundHostInterfaceForTracking {
var t : Outbound? = nil
init() {
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
func calcFeatureVector(bitmap: Any?) -> KotlinArray<KotlinArray<KotlinFloatArray>>? {
do {
var test : Any? = (bitmap as! Bitmap).bitmap
return nil
} catch {
return nil
}
}
}
TrackingWrapper 看起来像这样:
class TrackingWrapper : NSObject {
static var instance: TrackingWrapper? = nil
var inbound: Inbound? = nil
var worker: Worker
override init() {
self.worker = Worker()
super.init()
initInboundInterface()
}
func initInboundInterface() {
runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
HostInterfaceForTracking()
}
}
func runOnMatchingLibraryThread(block: @escaping() -> Void) {
worker.enqueue {
block()
}
}
}
需要函数 runOnMatchingLibraryThread
因为每次调用 TrackingLibrary 都需要从完全相同的线程调用,所以 Worker
class 初始化一个线程并将每个方法排入队列以那个线程。
Bitmap
在这种情况下只是 UIImage
的包装器,我已经通过 .bitmap
调用访问了它,所以我尝试访问包装的 UIImage
并保存它在 test
变量中。该库每隔几帧从 Swift 一侧获取当前相机帧,并将当前图像包装为 Bitmap
发送到此处描述的方法 calcFeatureVector
。
问题: 我的内存负载在应用程序启动后立即开始增加,直到它崩溃。如果我不访问包装的 UIImage
(var test : Any? = (bitmap as! Bitmap)
),情况就不是这样了。因此,仅通过访问 Swift 侧的包装变量,就会出现巨大的内存泄漏。有什么我遗漏的或者有什么方法可以释放内存吗?
看起来你在这里有循环依赖:
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
您要求 HostInterfaceForTracking
中的 属性 保持对 HostInterfaceForTracking
相同实例的强引用。您应该使用 [weak self]
来避免循环引用。
编辑:
好的,看完你们其他人的代码后,有很多东西要解压。 classes、函数和线程之间有很多不必要的来回跳动。
- 无需使用
runOnMatchingLibraryThread
即可创建某物的实例。您只需要将它用于处理图像本身的代码(我假设,到目前为止我还没有看到任何需要拆分到另一个线程的东西)。在 TrackingWrapper
中,您可以更轻松地创建单例,并通过简单地在第一行执行此操作来匹配 swift 模式:
static let shared = TrackingWrapper()
而且你想在任何地方使用它,只需调用 TrackingWrapper.shared
。这是更常见的,将避免代码中的间接级别之一。
我不确定 Worker
或 Inbound
是什么,但是这些可以而且应该在 TrackingWrapper init 中创建,而不是分支 Inbound
的初始化,使用另一个线程。
在 initInboundInterface
中,您正在创建一个 HostInterfaceForTracking()
的实例,它不会存储在任何地方。 HostInterfaceForTracking 在创建后继续留在内存中的唯一原因是因为它内部的循环依赖。这 100% 会导致某种形式的内存问题。这也应该是 TrackingWrapper 上的一个 属性,同样,它的 Init 不应该在 runOnMatchingLibraryThread
.
中调用
有 HostInterfaceForTracking
的初始化,也使用 runOnMatchingLibraryThread
是有问题的。如果我们内联所有代码,发生的事情是这样的:
TrackingWrapper
init() {
self.runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
}
让所有这些 class 不必要地继续返回 TrackingWrapper 会导致问题。
- 在
HostInterfaceForTracking
的 init 中,不需要在单独的线程上创建 Outbound。 class 中的第一行可以简单地是:
var t : Outbound = OutBound()
如果您愿意,也可以在初始化时执行。无论哪种方式也将消除在使用前需要解包 Outbound 的问题。
- 在
Outbound
中,您存储了 2 个对 hostInterface 实例的引用:
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
我本以为应该只有1个。如果现在有一个class的多个副本,它有一个循环依赖,它有多个调用单独的线程。这又会引起问题。
- 我仍然不确定 Swift 和 Kotlin 之间的区别。在 Swift 中,当将
self
传递给要存储的函数时,存储它的 class 会将 属性 标记为 weak
,如下所示:
weak var hostInterface: ......
这将避免形成任何循环依赖。快速 google 说这不是 Kotlin 中的工作方式。最好查看传递闭包的 swift 端(kotlin 上的 lambda)和执行它的 kotlin 端。这可能会避免存储强引用的需要。否则,您需要查看代码的某些部分,将 hostInterface 设置回 null。同样很难说只看到了一些代码而不知道它是如何工作的。
简而言之,代码看起来过于复杂,需要简化,以便更容易地跟踪所有这些移动的片段。
我正在使用 Kotlin/Native 构建一个 Kotlin 库以在我的 iOS 应用中使用。在我从 Swift 调用库中的一些方法后,我还想从库中调用 Swift 中的方法。为此,我在库中实现了一个接口:
class Outbound {
interface HostInterfaceForTracking {
fun calcFeatureVector(bitmap: Any?): Array<Array<FloatArray>>?
}
var hostInterface: HostInterfaceForTracking? = null
fun registerInterface(hostInterface: HostInterfaceForTracking) {
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
}
}
这是在 Swift 端实现的,如下所示:
class HostInterfaceForTracking : OutboundHostInterfaceForTracking {
var t : Outbound? = nil
init() {
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
func calcFeatureVector(bitmap: Any?) -> KotlinArray<KotlinArray<KotlinFloatArray>>? {
do {
var test : Any? = (bitmap as! Bitmap).bitmap
return nil
} catch {
return nil
}
}
}
TrackingWrapper 看起来像这样:
class TrackingWrapper : NSObject {
static var instance: TrackingWrapper? = nil
var inbound: Inbound? = nil
var worker: Worker
override init() {
self.worker = Worker()
super.init()
initInboundInterface()
}
func initInboundInterface() {
runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
HostInterfaceForTracking()
}
}
func runOnMatchingLibraryThread(block: @escaping() -> Void) {
worker.enqueue {
block()
}
}
}
需要函数 runOnMatchingLibraryThread
因为每次调用 TrackingLibrary 都需要从完全相同的线程调用,所以 Worker
class 初始化一个线程并将每个方法排入队列以那个线程。
Bitmap
在这种情况下只是 UIImage
的包装器,我已经通过 .bitmap
调用访问了它,所以我尝试访问包装的 UIImage
并保存它在 test
变量中。该库每隔几帧从 Swift 一侧获取当前相机帧,并将当前图像包装为 Bitmap
发送到此处描述的方法 calcFeatureVector
。
问题: 我的内存负载在应用程序启动后立即开始增加,直到它崩溃。如果我不访问包装的 UIImage
(var test : Any? = (bitmap as! Bitmap)
),情况就不是这样了。因此,仅通过访问 Swift 侧的包装变量,就会出现巨大的内存泄漏。有什么我遗漏的或者有什么方法可以释放内存吗?
看起来你在这里有循环依赖:
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
您要求 HostInterfaceForTracking
中的 属性 保持对 HostInterfaceForTracking
相同实例的强引用。您应该使用 [weak self]
来避免循环引用。
编辑:
好的,看完你们其他人的代码后,有很多东西要解压。 classes、函数和线程之间有很多不必要的来回跳动。
- 无需使用
runOnMatchingLibraryThread
即可创建某物的实例。您只需要将它用于处理图像本身的代码(我假设,到目前为止我还没有看到任何需要拆分到另一个线程的东西)。在TrackingWrapper
中,您可以更轻松地创建单例,并通过简单地在第一行执行此操作来匹配 swift 模式:
static let shared = TrackingWrapper()
而且你想在任何地方使用它,只需调用 TrackingWrapper.shared
。这是更常见的,将避免代码中的间接级别之一。
我不确定
Worker
或Inbound
是什么,但是这些可以而且应该在 TrackingWrapper init 中创建,而不是分支Inbound
的初始化,使用另一个线程。在
中调用initInboundInterface
中,您正在创建一个HostInterfaceForTracking()
的实例,它不会存储在任何地方。 HostInterfaceForTracking 在创建后继续留在内存中的唯一原因是因为它内部的循环依赖。这 100% 会导致某种形式的内存问题。这也应该是 TrackingWrapper 上的一个 属性,同样,它的 Init 不应该在runOnMatchingLibraryThread
.有
HostInterfaceForTracking
的初始化,也使用runOnMatchingLibraryThread
是有问题的。如果我们内联所有代码,发生的事情是这样的:
TrackingWrapper
init() {
self.runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
}
让所有这些 class 不必要地继续返回 TrackingWrapper 会导致问题。
- 在
HostInterfaceForTracking
的 init 中,不需要在单独的线程上创建 Outbound。 class 中的第一行可以简单地是:
var t : Outbound = OutBound()
如果您愿意,也可以在初始化时执行。无论哪种方式也将消除在使用前需要解包 Outbound 的问题。
- 在
Outbound
中,您存储了 2 个对 hostInterface 实例的引用:
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
我本以为应该只有1个。如果现在有一个class的多个副本,它有一个循环依赖,它有多个调用单独的线程。这又会引起问题。
- 我仍然不确定 Swift 和 Kotlin 之间的区别。在 Swift 中,当将
self
传递给要存储的函数时,存储它的 class 会将 属性 标记为weak
,如下所示:
weak var hostInterface: ......
这将避免形成任何循环依赖。快速 google 说这不是 Kotlin 中的工作方式。最好查看传递闭包的 swift 端(kotlin 上的 lambda)和执行它的 kotlin 端。这可能会避免存储强引用的需要。否则,您需要查看代码的某些部分,将 hostInterface 设置回 null。同样很难说只看到了一些代码而不知道它是如何工作的。
简而言之,代码看起来过于复杂,需要简化,以便更容易地跟踪所有这些移动的片段。