1

我正在创建我的第一个 kotlin 多平台项目,我在快速使用 kotlin 流程时遇到了一些困难。我使用 kotlin flow 和 ktor 创建了模型、服务器数据请求作为通用文件、视图模型和我创建为本机的 ui 层。所以,我没有快速开发的经验,除此之外,我在快速视图模型上使用流程时遇到了很多麻烦。在寻找我的问题的答案时,我发现了一个描述为 CommonFlow 的类,它的目的是用作两种语言的通用代码(kotlin、swift,但我遇到了一个错误,让我很少或根本不知道为什么会这样发生了,或者,可能只是我缺乏对 xcode 和 swift 编程的控制:

所以这是 xcode 指出错误的代码部分: 在此处输入图像描述 Obs: sorry about the image 我想这次可能更具描述性

这就是我从错误中得到的全部

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)

4

1 回答 1

0

有一个在 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.
于 2021-05-29T03:40:32.160 回答