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、函数和线程之间有很多不必要的来回跳动。

  1. 无需使用 runOnMatchingLibraryThread 即可创建某物的实例。您只需要将它用于处理图像本身的代码(我假设,到目前为止我还没有看到任何需要拆分到另一个线程的东西)。在 TrackingWrapper 中,您可以更轻松地创建单例,并通过简单地在第一行执行此操作来匹配 swift 模式:
static let shared = TrackingWrapper()

而且你想在任何地方使用它,只需调用 TrackingWrapper.shared。这是更常见的,将避免代码中的间接级别之一。

  1. 我不确定 WorkerInbound 是什么,但是这些可以而且应该在 TrackingWrapper init 中创建,而不是分支 Inbound的初始化,使用另一个线程。

  2. initInboundInterface 中,您正在创建一个 HostInterfaceForTracking() 的实例,它不会存储在任何地方。 HostInterfaceForTracking 在创建后继续留在内存中的唯一原因是因为它内部的循环依赖。这 100% 会导致某种形式的内存问题。这也应该是 TrackingWrapper 上的一个 属性,同样,它的 Init 不应该在 runOnMatchingLibraryThread.

    中调用
  3. 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 会导致问题。

  1. HostInterfaceForTracking 的 init 中,不需要在单独的线程上创建 Outbound。 class 中的第一行可以简单地是:
var t : Outbound = OutBound()

如果您愿意,也可以在初始化时执行。无论哪种方式也将消除在使用前需要解包 Outbound 的问题。

  1. Outbound 中,您存储了 2 个对 hostInterface 实例的引用:
this.hostInterface = hostInterface
instance.hostInterface = hostInterface

我本以为应该只有1个。如果现在有一个class的多个副本,它有一个循环依赖,它有多个调用单独的线程。这又会引起问题。

  1. 我仍然不确定 Swift 和 Kotlin 之间的区别。在 Swift 中,当将 self 传递给要存储的函数时,存储它的 class 会将 属性 标记为 weak,如下所示:
weak var hostInterface: ......

这将避免形成任何循环依赖。快速 google 说这不是 Kotlin 中的工作方式。最好查看传递闭包的 swift 端(kotlin 上的 lambda)和执行它的 kotlin 端。这可能会避免存储强引用的需要。否则,您需要查看代码的某些部分,将 hostInterface 设置回 null。同样很难说只看到了一些代码而不知道它是如何工作的。

简而言之,代码看起来过于复杂,需要简化,以便更容易地跟踪所有这些移动的片段。