在所有成员初始化之前由闭包捕获的 Swift5(+RxSwift) 'self'

Posted

技术标签:

【中文标题】在所有成员初始化之前由闭包捕获的 Swift5(+RxSwift) \'self\'【英文标题】:Swift5(+RxSwift) 'self' captured by a closure before all members were initialized在所有成员初始化之前由闭包捕获的 Swift5(+RxSwift) 'self' 【发布时间】:2019-05-08 11:27:01 【问题描述】:

我正在学习 MVVM + RxSwift。 我想保存一个值以在下一个流程中重用它。 但是我遇到了上面的错误。

我假设我初始化了“translatedText”的值,但错误仍然出现。

我已经尝试了一些方法来初始化声明等等......


import UIKit
import RxSwift

final class ViewModel 


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>
    var translatedText: String
    // var translatedText: String = "" //this case also dosn't work

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) 

        self.api = api
        self.translatedText = "" //I guess I initialised this here

        let event = inputObservable
            .flatMap  input -> Observable<Event<Void>> in
                if let text = input 
                    self.translatedText = text // error. i want to save "input" to use at "let tapEvent"
                
                return model
                    .validate(text: input)
                    .materialize()
            
            .share()

        self.validationText = event
            .flatMap  event -> Observable<String> in
                switch event 
                case .next:
                    return .just("")
                case let .error(error as ModelError):
                    return .just(error.errorLabel)
                case .error, .completed:
                    return .empty()
                
            
            .startWith(ModelError.invalidBlank.errorLabel)

        let tapEvent = changeButtonClicked
            .flatMap  (result) -> Observable<Event<String>> in
                return api
                    .fetch(text: self.translatedText) // I want use it here
                    .materialize()
        
        .share()

        self.getObservable = tapEvent
            .flatMap  event -> Observable<String> in
                switch event 
                case .next:
                    return .just(event.element!)
                case let .error(error as ModelError):
                    return .just(error.errorLabel)
                case .error, .completed:
                    return .empty()
                
            
    



你们有更好的解决方案吗?

-----自行解决-----

我只是在 let 事件之前创建了一个临时变量,它可以按我的意愿工作。

final class ViewModel 


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) 

        self.api = api
        var temp = ""

        let event = inputObservable
            .flatMap  input -> Observable<Event<Void>> in
                if let text = input 
                    temp = text
                
                return model
                    .validate(text: input)
                    .materialize()
            
            .share()

【问题讨论】:

【参考方案1】:

您不应该使用一些随机的“临时”内存块将两个独立的可观察流相互联系起来。这两个流依赖于inputObservable,这应该说清楚。此外,您的视图模型可以大大简化...

final class ViewModel 
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) 

        let translatedText = inputObservable.share() // this line is unnecessary if `inputObservable` is already hot.

        self.validationText = translatedText
            .flatMap  input in
                return model
                    .validate(text: input)
                    .map  "" 
                    .catchError  error in .just((error as? ModelError)?.errorLabel ?? error.localizedDescription) 
            
            .startWith(ModelError.invalidBlank.errorLabel)

        self.getObservable = changeButtonClicked
            .withLatestFrom(translatedText)
            .compactMap  $0 
            .flatMap  translatedText in
                return api
                    .fetch(text: translatedText)
                    .catchError  error in .just((error as? ModelError)?.errorLabel ?? error.localizedDescription) 
            
    

【讨论】:

【参考方案2】:

您的问题是,当您调用 flatMap 时,self 没有完全初始化。在您的情况下,validationTextgetObservable 处于未定义状态。

最快的方法是使它们隐式解包,但您确实必须保证在初始化之前它们不会被访问:

final class ViewModel 


    private let api: apiProtocol
    let validationText: Observable<String>!
    let getObservable: Observable<String>!
    var translatedText: String!

    // ...

【讨论】:

感谢您的评论。这两个由self.validationText = eventself.getObservable = tapEvent 初始化,所以问题是self 在初始化之前在闭包中用作self.translatedText。但我可以通过简单地执行上面的代码来解决它。我找到了另一种使用lazy 的方法,但这次不是。 如果您希望您的应用程序无崩溃,则应避免这样做。【参考方案3】:

---- 自行解决-----

我只是在 let 事件之前创建了一个临时变量,它可以按我的意愿工作。

final class ViewModel 


    private let api: apiProtocol
    let validationText: Observable<String>
    let getObservable: Observable<String>

    init(inputObservable: Observable<String?>, changeButtonClicked: Observable<Void>, model: ModelProtocol, api: apiProtocol = APICntl()) 

        self.api = api
        var temp = ""

        let event = inputObservable
            .flatMap  input -> Observable<Event<Void>> in
                if let text = input 
                    temp = text
                
                return model
                    .validate(text: input)
                    .materialize()
            
            .share()

【讨论】:

以上是关于在所有成员初始化之前由闭包捕获的 Swift5(+RxSwift) 'self'的主要内容,如果未能解决你的问题,请参考以下文章

在初始化之前由闭包捕获的变量'isVericated' - Swift

'self'由关闭错误捕获&&,||操作者

Swift 5:转义闭包捕获'inout'参数

C#由变量捕获引起对闭包的思考

由闭包错误自我捕获

Swift 中的Closures(闭包)详解