我如何在 swift Kotlin 多平台上使用 Flow?
Posted
技术标签:
【中文标题】我如何在 swift Kotlin 多平台上使用 Flow?【英文标题】:How i can use Flow on swift Kotlin multiplatform? 【发布时间】:2021-08-08 21:13:31 【问题描述】:我正在创建我的第一个 kotlin 多平台项目,但在快速使用 kotlin 流程时遇到了一些困难。我使用 kotlin flow 和 ktor 创建了模型、服务器数据请求作为通用文件、视图模型和我创建为本机的 ui 层。所以,我没有快速开发的经验,除此之外,我在快速视图模型上使用流程时遇到了很多麻烦。在寻找我的问题的答案时,我发现了一个描述为 CommonFlow 的类,它的目的是用作两种语言的通用代码(kotlin、swift,但我遇到了一个错误,让我很少或根本不知道为什么会这样发生了,或者,可能只是我缺乏对 xcode 和 swift 编程的支配:
所以这是 xcode 指出错误的代码部分: Obs:对图片感到抱歉,我认为这次可能更具描述性
这就是我从错误中得到的全部
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee5928ff8)
我的 ios 视图模型:
class ProfileViewModel: ObservableObject
private let repository: ProfileRepository
init(repository: ProfileRepository)
self.repository = repository
@Published var authentication: Authetication = Authetication.unauthenticated(false)
@Published var TokenResponse: ResponseDTO<Token>? = nil
@Published var loading: Bool = false
func authenticate(email: String, password: String)
DispatchQueue.main.async
if(self.isValidForm(email: email, password: password))
self.repository.getTokenCFlow(email: email, password: password).watch response in
switch response?.status
case StatusDTO.success:
self.loading = false
let token: String = response!.data!.accessToken!
SecretStorage().saveToken(token: token)
self.authentication = Authetication.authenticated
break;
case StatusDTO.loading:
self.loading = true
break;
case StatusDTO.error:
print("Ninja request error \(String(describing: response!.error!))")
break;
default:
break
private func isValidForm(email: String, password: String) -> Bool
var invalidFields = [Pair]()
if(!isValidEmail(email))
invalidFields.append(Pair(first:"email invalido",second: "email invalido"))
if(password.isEmpty)
invalidFields.append(Pair(first:"senha invalida",second: "senha invalida"))
if(!invalidFields.isEmpty)
self.authentication = Authetication.invalidAuthentication(invalidFields)
return false
return true
private func isValidEmail(_ email: String) -> Bool
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]2,64"
let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailPred.evaluate(with: email)
class Pair
let first: String
let second: String
init(first:String, second: String)
self.first = first
self.second = second
enum Authetication
case invalidAuthentication([Pair])
case authenticated
case persistentAuthentication
case unauthenticated(Bool)
case authenticationFailed(String)
存储库方法:
override fun getToken(email: String, password: String): Flow<ResponseDTO<Token>> = flow
emit(ResponseDTO.loading<Token>())
try
val result = api.getToken(GetTokenBody(email, password))
emit(ResponseDTO.success(result))
catch (e: Exception)
emit(ResponseDTO.error<Token>(e))
@InternalCoroutinesApi
override fun getTokenCFlow(email: String, password: String): CFlow<ResponseDTO<Token>>
return wrapSwift(getToken(email, password))
类 CFLOW:
@InternalCoroutinesApi
class CFlow<T>(private val origin: Flow<T>): Flow<T> by origin
fun watch(block: (T) -> Unit): Closeable
val job = Job()
onEach
block(it)
.launchIn(CoroutineScope(Dispatchers.Main + job))
return object: Closeable
override fun close()
job.cancel()
@FlowPreview
@ExperimentalCoroutinesApi
@InternalCoroutinesApi
fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())
@InternalCoroutinesApi
fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)
@InternalCoroutinesApi
fun <T> wrapSwift(flow: Flow<T>): CFlow<T> = CFlow(flow)
【问题讨论】:
【参考方案1】:有一个在 KampKit 中使用流的例子
https://github.com/touchlab/KaMPKit
我将粘贴来自 NativeViewModel (iOS) 的摘录
class NativeViewModel(
private val onLoading: () -> Unit,
private val onSuccess: (ItemDataSummary) -> Unit,
private val onError: (String) -> Unit,
private val onEmpty: () -> Unit
) : KoinComponent
private val log: Kermit by inject parametersOf("BreedModel")
private val scope = MainScope(Dispatchers.Main, log)
private val breedModel: BreedModel = BreedModel()
private val _breedStateFlow: MutableStateFlow<DataState<ItemDataSummary>> = MutableStateFlow(
DataState.Loading
)
init
ensureNeverFrozen()
observeBreeds()
@OptIn(FlowPreview::class)
fun observeBreeds()
scope.launch
log.v "getBreeds: Collecting Things"
flowOf(
breedModel.refreshBreedsIfStale(true),
breedModel.getBreedsFromCache()
).flattenMerge().collect dataState ->
_breedStateFlow.value = dataState
This ViewModel is consumed in swift like this:
lazy var adapter: NativeViewModel = NativeViewModel(
onLoading: /* Loading spinner is shown automatically on iOS */
[weak self] in
guard let self = self else return
if (!(self.refreshControl.isRefreshing))
self.refreshControl.beginRefreshing()
,
onSuccess:
[weak self] summary in self?.viewUpdateSuccess(for: summary)
self?.refreshControl.endRefreshing()
,
onError: [weak self] error in self?.errorUpdate(for: error)
self?.refreshControl.endRefreshing()
,
onEmpty: /* Show "No doggos found!" message */
[weak self] in self?.refreshControl.endRefreshing()
)
In short, the flow is kept wrapped in kotlin mp land and leveraged in iOS by using traditional callback interfaces.
【讨论】:
以上是关于我如何在 swift Kotlin 多平台上使用 Flow?的主要内容,如果未能解决你的问题,请参考以下文章
如何将字节从 Swift (iOS) 传递到 Kotlin 通用模块?
如何在 Kotlin 标准库(多平台)上获取当前的 unixtime
如何在 kotlin Multiplatform 和 Swift 中使用默认接口实现