kotlin 为什么iOS委托在KMM中不能正常工作?

bjp0bcyl  于 2023-11-21  发布在  Kotlin
关注(0)|答案(1)|浏览(194)

我有一个KMM项目,需要访问用户的位置。我的locationClient是用Kotlin写的。
这一切都像预期的那样工作,但我在将Kotlin从1.8.20更新到1.9.20后看到了奇怪的行为。
我得到了预期的前几个位置,但之后代理就停止发送位置了。我通常在它停止之前得到5或6个位置,从来没有超过10个。我在模拟器和真实的设备上测试过这个,得到了相同的结果。
在Swift端,开始发送位置的触发器位于一个“onAppear”块中,导航离开和返回会触发另外几个位置通过,但随后它们再次停止。
当位置停止时,手机仍然认为应用程序正在收听位置,并继续显示相应的隐私通知。当我导航离开并停止收听位置时,这将被正确删除。
地点客户:

actual class LocationProviderImpl : LocationProvider {
     private val locationManager = CLLocationManager()

    private class LocationDelegate : NSObject(), CLLocationManagerDelegateProtocol {
        var onLocationUpdate: ((Location?) -> Unit)? = null
        override fun locationManager(manager: CLLocationManager, didUpdateLocations: List<*>) {
//This is correctly called for the first few locations then stops
            didUpdateLocations.firstOrNull()?.let {
                val location = it as CLLocation
                location.coordinate.useContents {
                    onLocationUpdate?.invoke(Location(latitude, longitude, it.altitude))
                }
            }
        }

        override fun locationManager(manager: CLLocationManager, didFailWithError: NSError) {
//This never gets called
            onLocationUpdate?.invoke(null)
        }
    }
override fun getCurrentLocationAsFlow(): Flow<Location?> = callbackFlow {
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        val locationDelegate = LocationDelegate()
        locationDelegate.onLocationUpdate = { location ->
            trySend(
                location
            ).isSuccess
        }
        locationManager.delegate = locationDelegate
         locationManager.startUpdatingLocation()
       awaitClose {
//This is never called
            locationManager.stopUpdatingLocation()
        }
    }.flowOn(Dispatchers.IO)

    fun requestBackground() {
        locationManager.allowsBackgroundLocationUpdates = true
    }

    fun stopBackground() {
        locationManager.allowsBackgroundLocationUpdates = false
    }
    fun stopLocationUpdates() {
        locationManager.stopUpdatingLocation()
    }
}

字符串
其使用方法如下:

object GpsService {

    private lateinit var locationClient: LocationProviderImpl
    private var initialized = false
    private var serviceScope = CoroutineScope(Dispatchers.IO)
 
 fun initialize(appModule: AppModule) {
        if (!initialized) {
            locationClient = LocationProviderImpl()
}
}
    

  fun startLocationListener() {
  locationClient.getCurrentLocationAsFlow().cancellable()
            .catch { e ->
                e.printStackTrace()
            }
            .mapNotNull { it }
            .onEach { location ->
                // send location to view
                _state.update {
                    it.copy(
                        currentLocation = location,
                        formattedAltitude = getFormattedAltitude(location.altitude)
                    )
                }
     }.launchIn(serviceScope)
    }


我没有得到任何错误或警告(并且到处都设置了断点/ println()),也没有错误或catch块被命中,我只是在最初的几个之后停止获取任何位置。这一切都在Kotlin1.8.20中运行得很好!

gzjq41n4

gzjq41n41#

Kotlin有它自己的内存模型,但是当你从Kotlin代码创建ObjC对象时,它们遵循ObjC内存模型。
在iOS世界中,名为delegate的字段通常(总是在系统框架中)存储一个对象的弱引用-这意味着这个对象不负责对象的生命周期,所以当对象被销毁时,该属性会自动设置为null。这样做是为了防止保留周期。有关详细信息,请参阅this question。如果您计划从Kotlin,查看一些关于自动引用计数(ARC)的文章。
在您的代码中,locationDelegate将在awaitClose暂停函数执行时立即释放。
所以你需要把它存储在某个地方。比如,在一个类变量中。
对于您当前的代码,这可能是棘手的,因为getCurrentLocationAsFlow可以从不同的地方被多次调用,因此旧的委托将被新的调用覆盖-您可以考虑在init期间创建一个委托并存储为let,并为该对象添加onLocationUpdate处理程序。

相关问题