第一步实现@State,@Binding一样声明属性
我们可以使用Swwift新出的@propertyWrapper来实现代码如下
@propertyWrapper
struct Behavior<Value> {
var wrappedValue:Value {
get{
try! _subject.value()
}
set{
_subject.onNext(newValue)
}
}
private let _subject:BehaviorSubject<Value>
init(wrappedValue: Value){
_subject = BehaviorSubject(value: wrappedValue)
}
init(initialValue: Value){
_subject = BehaviorSubject(value: initialValue)
}
}
struct Test {
@Behavior var user:Bool = true
}
第二步实现@State,@Binding一样用$加属性名称可以获取属性包装本身
申明一个 projectedValue可读属性就可以实现。返回值类型就是$可以获取到的类型
var projectedValue:Behavior<Value> { self }
第三步实现ObservableType协议,扩展ObservableType实现几个绑定方法
extension ObservableType {
func bind(to relays: Behavior<Element>...) -> Disposable {
bind(to: relays)
}
func bind(to relays: Behavior<Element?>...) -> Disposable {
map { $0 as Element? }.bind(to: relays)
}
private func bind(to relays: [Behavior<Element>]) -> Disposable {
subscribe { e in
switch e {
case let .next(element):
relays.forEach {
$0.wrappedValue = element
}
case let .error(error):
print("Behavior error to publish relay: \(error)")
case .completed:
break
}
}
}
}
上面代码会爆一个错误$0.wrappedValue = element Behavior是一个结构体,遍历时候是不可变的当给他属性赋值的时候会
Cannot assign to property: '$0' is immutable。
我们用nonmutating,来告诉编译器不会修改实例内部的值,也就是set时,不会改变任何其他的变量。
var wrappedValue:Value {
get{
try! _subject.value()
}
nonmutating set{
_subject.onNext(newValue)
}
}
现在只剩最后一步,我们先看一下SwiftUI中的有段代码
@State var size = CGSize()
func test(){
let height:Binding<CGFloat> = $size.height
}
仔细观察发现$size.height也是包装器类型,我们现在来实现它
我们需要知道这个@dynamicMemberLookup它是swift4.2出的,具体详情问度。
在我们案例中实现如下
private let disposeBag = DisposeBag()
subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Value, Subject>)->Behavior<Subject>{
var recursive = true
let behavior = Behavior<Subject>(wrappedValue: wrappedValue[keyPath:keyPath])
self.map{ $0[keyPath:keyPath] }
.filter({ (obj) -> Bool in
if recursive {
return false
}
recursive = true
return true
})
.bind(to: behavior)
.disposed(by: disposeBag)
behavior.filter({ (obj) -> Bool in
if recursive {
recursive = false
return false
}
recursive = true
return true
}).withLatestFrom(self) { (obj1, obj2) -> Value in
var value = obj2
value[keyPath:keyPath] = obj1
return value
}
.bind(to: self)
.disposed(by: disposeBag)
return behavior
}
使用recursive是防止递归
最后我们使用它做简单用户资料提交案例
界面如下

class ViewController: UITableViewController {
@IBOutlet weak var submitButton: UIButton!
@IBOutlet weak var mobileTextField: UITextField!
@IBOutlet weak var hobbyTextField: UITextField!
@IBOutlet weak var ageTextField: UITextField!
@IBOutlet weak var nameTextField: UITextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
mobileTextField.rx.shouldChangeCharacters = { (textField,range,string) ->Bool in
///这里可以做一些判断输入规则
return true
}
nameTextField.rx.text <-> $user.name
ageTextField.rx.text <-> $user.age
hobbyTextField.rx.text <-> $user.hobby
mobileTextField.rx.text <-> $user.mobile
$user.map{
if let name = $0.name,name.count > 0,
let age = $0.age,age.count > 0,
let hobby = $0.hobby,hobby.count > 0,
let mobile = $0.mobile,mobile.count > 0{
return true
}
return false
}
.bind(to: submitButton.rx.isEnabled)
.disposed(by: disposeBag)
$user.subscribe { (event) in
print(event)
}.disposed(by: disposeBag)
}
@Behavior var user = User()
struct User:Codable {
var name:String?
var age:String?
var hobby:String?
var mobile:String?
}
}
class TextFieldDelegateProxy: DelegateProxy<UITextField,UITextFieldDelegate>,DelegateProxyType,UITextFieldDelegate
{
init(textField:ParentObject) {
super.init(parentObject: textField, delegateProxy: TextFieldDelegateProxy.self)
}
static func registerKnownImplementations() {
self.register{ TextFieldDelegateProxy(textField: $0) }
}
static func currentDelegate(for object: UITextField) -> UITextFieldDelegate? {
object.delegate
}
static func setCurrentDelegate(_ delegate: UITextFieldDelegate?, to object: UITextField) {
object.delegate = delegate
}
typealias ShouldChangeCharactersBlock = (UITextField,NSRange,String)->Bool
var shouldChangeCharactersIn:ShouldChangeCharactersBlock?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
shouldChangeCharactersIn?(textField,range,string) ?? true
}
}
extension Reactive where Base:UITextField {
typealias ShouldChangeCharactersBlock = (UITextField,NSRange,String)->Bool
var shouldChangeCharacters:ShouldChangeCharactersBlock?{
set{
TextFieldDelegateProxy.proxy(for: base).shouldChangeCharactersIn = newValue
}
get{
TextFieldDelegateProxy.proxy(for: base).shouldChangeCharactersIn
}
}
}
infix operator <-> : DefaultPrecedence
@discardableResult
func <-> (property: ControlProperty<String?>, relay: Behavior<String?>) ->Disposable {
let bindToUIDisposable = property.orEmpty.filter{ $0.count > 0 }
.bind(to: relay)
let bindToRelay = relay.bind(to: property)
return Disposables.create(bindToUIDisposable, bindToRelay)
}
@discardableResult
func <-> (property: ControlProperty<String?>, relay: Behavior<String>) ->Disposable {
let bindToUIDisposable = property.orEmpty.filter{ $0.count > 0 }
.bind(to: relay)
let bindToRelay = relay.bind(to: property)
return Disposables.create(bindToUIDisposable, bindToRelay)
}