Я создаю библиотеку Kotlin для использования в своем приложении iOS, используя Kotlin/Native. После того, как я вызываю некоторые методы в библиотеке из 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
инициализирует поток и ставит в очередь каждый метод этого потока.
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]
, чтобы избежать циклической ссылки.
Обновлено:
Хорошо, увидев остальную часть вашего кода, есть много чего распаковать. Существует много ненужного переключения между классами, функциями и потоками.
runOnMatchingLibraryThread
, чтобы просто создать экземпляр чего-либо. Вам нужно использовать это только для кода, обрабатывающего само изображение (я предполагаю, что до сих пор я не видел ничего, что требовало бы разделения на другой поток). Внутри TrackingWrapper
вы можете проще создать синглтон и сопоставить шаблон Swift, просто сделав это в качестве первой строки:static let shared = TrackingWrapper()
И везде, где вы хотите его использовать, вы можете просто вызвать TrackingWrapper.shared
. Это более распространено и позволит избежать одного из уровней косвенности в коде.
Я не уверен, что такое Worker
или Inbound
, но опять же, их можно и нужно создавать внутри инициализации TrackingWrapper, а не разветвлять инициализацию 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)
}
}
}
Если все эти классы будут без необходимости возвращаться в TrackingWrapper, это вызовет проблемы.
HostInterfaceForTracking
нет необходимости создавать Outbound в отдельном потоке. Первая строка в этом классе может быть просто:var t : Outbound = OutBound()
Или сделайте это в init, если хотите. В любом случае также будет устранена проблема необходимости развернуть Outbound перед его использованием.
Outbound
вы храните 2 ссылки на экземпляр hostInterface:this.hostInterface = hostInterface
instance.hostInterface = hostInterface
Я бы предположил, что должно быть только 1. Если сейчас есть несколько копий класса, который имеет циклическую зависимость, которая имеет несколько вызовов для отдельных потоков. Это снова вызовет проблемы.
self
в функцию для сохранения класс, хранящий его, пометит свойство как weak
, например:weak var hostInterface: ......
Что позволит избежать формирования круговой зависимости. Быстрый поиск в Google говорит, что в Котлине все работает иначе. Возможно, было бы лучше взглянуть на сторону Swift, проходящую через замыкание (лямбда на kotlin), и на сторону kotlin, выполняющую это. Это может избежать необходимости хранить сильную ссылку. В противном случае вам нужно изучить какую-то часть вашего кода, устанавливающую hostInterface обратно на ноль. Опять же, немного сложно сказать, только видя часть кода и не зная, как он работает.
Короче говоря, похоже, что код слишком сложен, и его нужно упростить, чтобы все эти движущиеся части можно было легче отслеживать.