Signal và Observable hoạt động như thế nào?

Bài toán

trong Reactive Cocoa ở bài trước link .ta có thể sử dụng để bắt sự kiện khi UITextField thay đổi rồi sẽ đưa ra một hành động nào đấy(ví dụ như UITextField có chứa @gmail.com thì đổi màu UITextField), có lẽ cũng it người chọc thẳng vào thư viện xem nó viết thế nào, vì thế mình viết 1 bài hướng dẫn nhỏ để các bạn có thể hiểu được cơ chế làm việc của Reactive sau này khi vào đọc Reactive các bạn có thể dễ dàng hơn trong việc tìm hiểu ý tưởng thiết kế của nó.

  • trước hết ta cần 1 enum để chia sự kiện chúng ta muốn bắt
// Observable Status
enum BoolState {
    // all statue
    case anyState
    // only true
    case onlyTrue
    // only false
    case onlyFalse
}

case anyState bắt tất cả sự kiện dù là true hay false case only true bắt duy nhất sự kiện true case onlyFalse bắt duy nhất sự kiện false

  • Tạo bộ phát tín hiệu (còn gọi là Signal hoặc Observable ).
/// Observable change
class ObservableSwitch {
    fileprivate var signals: [() -> Bool] = []
    fileprivate var boolState: BoolState
    var action:((_ status: Bool)->())?
    
    init(_ boolState: BoolState) {
        self.boolState = boolState
    }
    
    fileprivate func addSignal(_ signal: @escaping () -> Bool) {
        self.signals.append(signal)
    }
    
    fileprivate func validate() {
        var result = true
        // Neu mang singals co chua gia tri false => (result -> false)
        for check in signals {
            if (result && !check()) {
                result = false
                break
            }
        }
        // tuỳ theo boolState và result mà callback closure
        if boolState == .anyState {
            action?(result)
        }
        else if boolState == .onlyTrue && result {
            action?(true)
        }
        else if boolState == .onlyFalse && !result {
            action?(false)
        }
    }
}
  • Sử dụng Bộ tín hiệu lắng nghe các sự kiện trong UITextFieldDelegate và UITextViewDelegate.
// Observable UITextField
class ObservableTextField: UITextField, UITextFieldDelegate {
    fileprivate var _switches: [ObservableSwitch] = []
    var textDidChange:((_ text: String)->())?
    var count = 0
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
        delegate = self
    }
    
    func textFieldDidChange(_ textField: UITextField) {
        if let text = textField.text {
            count = textField.text!.characters.count
            textDidChange?(text)
            
            for s in self._switches {
                s.validate()
            }
        }
    }
    
    func addSignal(toSwitch: ObservableSwitch, _ signal: @escaping () -> Bool) {
        for s in self._switches where s === toSwitch {
            s.addSignal(signal)
            return
        }
        
        toSwitch.addSignal(signal)
        self._switches.append(toSwitch)
    }
}

// Observable UITextView
class ObservableTextView: UITextView, UITextViewDelegate {
    fileprivate var _switches: [ObservableSwitch] = []
    var textDidChange:((_ text: String) -> ())?
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        delegate = self
    }
    
    func textViewDidChange(_ textView: UITextView) {
        for s in self._switches {
            s.validate()
        }
        textDidChange?(textView.text)
    }
    
    func addSignal( toSwitch: ObservableSwitch, _ signal: @escaping () -> Bool) {
        for s in self._switches where s === toSwitch {
            s.addSignal(signal)
            return
        }
        
        toSwitch.addSignal(signal)
        self._switches.append(toSwitch)
    }
}
  • Sử dụng !
class ViewController: UIViewController {
    
    
    @IBOutlet weak fileprivate var lblName: UILabel!
    // MARK: Observale
    @IBOutlet weak fileprivate var tfName: ObservableTextField!
    @IBOutlet weak fileprivate var tvName: ObservableTextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.observetfName()
        self.observetvName()
        
    }
}

extension UIViewController {
    func showAlert(message: String) {
        let alertController = UIAlertController(title: "I know!", message: message, preferredStyle: .alert)
        let action = UIAlertAction(title: "OKE", style: .default, handler: nil)
        alertController.addAction(action)
        
        self.present(alertController, animated: true, completion: nil)

    }
}

extension ViewController {
    
    fileprivate func observetfName() {
        // lang nghe duy nhat su kien "true"
        let obSwitchtfName = ObservableSwitch(.onlyTrue)
        obSwitchtfName.action = { value in
            print(value)
            self.showAlert(message: "NguyenPhong!!!")
            
        }
        
        self.tfName.addSignal(toSwitch: obSwitchtfName) { [unowned self] () -> Bool in
            return self.tfName.text == "NguyenPhong"
        }
        self.tfName.textDidChange = {[unowned self] value in
            self.lblName.text = value
            self.tvName.text = value
        }
    }
    
    fileprivate func observetvName() {
        // lang nghe tat ca su kien "true" or "false"
        let obSwitchtvname = ObservableSwitch(.anyState)
        obSwitchtvname.action = { value in
            print(value)
            if value {
                self.showAlert(message: "IOS!!!")
            }
        }
        
        self.tvName.addSignal(toSwitch: obSwitchtvname) { [unowned self] () -> Bool in
            return self.tvName.text == "IOS"
        }
        
        self.tvName.textDidChange = { [unowned self] value in
            self.lblName.text = value
            self.tfName.text = value
        }
    }
}

Kết Github. Bài sau mình sẽ hướng dẫn các bạn làm cái Operator Binding giống cái <~ của thằng Reactive Swift, nói chung Reactive cũng dễ chứ cũng không khó quá như mọi người vẫn hay nghĩ, nó thật sự có sức mạnh nhưng không quá khó đến thế. Try and Try!

All Rights Reserved