一、需要背景
1、现状
以后组件化开发中,常常会用到MVVM设计模式,它促成了UI代码与业务逻辑的拆散,肯定水平解决viewController臃肿问题,但也使得数据绑定变得复杂,很多状况下须要咱们手动绑定数据和刷新界面,常常要写一堆零散的数据绑定业务代码。
对于数据绑定的复杂度问题,咱们齐全能够应用ReactiveCocoa框架(一个典型的函数响应式编程框架)解决,这里不做深刻理解,它尽管很好很弱小,但对于组件化开发来说还是供过于求,目前咱们仅仅须要一个轻量级的数据绑定框架。
2、指标
本人保护一个轻量级的数据绑定开源框架,例如CRDataBind(Chain Response Data Bind),它的接口调用反对链式语法,并通过响应式编程疾速实现数据绑定更新。
(本计划次要以老虎机抽奖demo对其进行实际和剖析,所用数据绑定框架原出自:github.com/shidavid/DV…,非常感谢作者的奉献!)
二、解决方案及亮点
1、计划概述
- 应用链式编程,反对多项绑定,反对单向/双向数据流;
- 反对过滤,某些条件下不更新绑定的数据;
- 反对数值与字符串主动转换,以及自定义数据接管格局;
- 只有反对KVC的对象都能实现数据绑定,不限定只能View和ViewModel;
- 无需依赖第三方,无需手动解绑,当指标对象内存开释时,CRDataBind主动解绑和开释。
2、问题难点
1)、如何通过链式语法一次绑定多个对象?
2)、如何通过响应式编程实现数据绑定?
3)、如何实现主动解绑?
3、剖析过程
1)、链式语法
在Objective-C中,咱们调用办法个别应用“[]”,简略的调用看起来过得去。但如果叠加很多层调用后,便不易浏览,常有漏掉某个“]”或“[”报错状况。
链式语法的外围是点语法。为了让OC在进行多层办法调用时,可能优雅和清晰的展现代码,咱们能够借鉴Swift、Masonary等的点语法模式。
示例:
// Swift:获取文件门路 let path: String = Bundle.main.path(forResource: "image", ofType: "jpg")! // Objective-C:Masonary布局更新 [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self); make.width.equalTo(@(self.buttonSize.width)).priorityLow(); make.height.equalTo(@(self.buttonSize.height)).priorityLow(); make.width.lessThanOrEqualTo(self); make.height.lessThanOrEqualTo(self); }];
点语法的要害是block,可借鉴Swift闭包的应用。它的非凡在于其自身能够帮忙办法进行参数传递,并返回数据,这样咱们便能够让办法一直返回实例自身,持续调用实例办法。
示例:
/**绑定3 model.winCode <---> winCodeLb.text <---> winCodeTF.text 减少fiter过滤中奖号大于3位数的响应 */ CRDataBind ._inout(self.lotteryVM.currentLottery, @"winCode") ._out(self.winCodeLb, @"text") ._inout_ui(self.winCodeTF, @"text", UIControlEventEditingChanged) ._filter(^BOOL(NSString *text) { // 减少过滤:中奖号码不超过3位数,界面上无奈配置大于3位数 return text.length <= 3; });
2)、响应式编程实现数据绑定
响应式编程是一种面向数据流和变动流传的编程范式,数据的输入输出(in&out)是要害,绑定-响应-刷新。数据inout的模式有:一般对象如target.property = value;UI对象如textField.text响应EditingChanged等等。
构想在同一个chain(响应链)中,咱们须要一个观察者,观察者通过弱援用缓存所察看对象。而后,监听一般对象,能够应用KVO;监听UI对象时,绑定对应UI事件。那么chain上所察看的某个对象属性变动时,咱们就能够遍历所有察看对象通过KVC(setValue:forkey:)进行更新操作。
3)、实现主动解绑
通过下面的剖析,咱们根本能实现接口的调用和理论数据绑定。接下来思考下:既然有绑定过程,那么对应的解绑也应该提供,而且最好是主动解绑,不须要内部手动去调用解绑和开释缓存。
应该如何触发解绑过程?比方target是进行数据绑定的对象,那么失常逻辑是target开释了,或者被动调用才进行解绑操作。咱们须要捕捉对象开释,现成的形式是利用dealloc办法,但咱们的目标是主动解绑,所以不应在绑定的所有内部对象dealloc中调用解绑。于是咱们能够思考为所有target实现一个NSObject分类,并通过runtime关联一个targetModel,当target开释后,model也跟着开释,此时咱们便能够在targetModel的dealloc中调用unbindWithTarget:进行解绑和开释缓存的操作。
三、具体设计
1、类图
image
2、代码原理分析
1)、A 与 B 双向数据绑定,Ain数据变动更新Aout、Bout数据,Bin同理。
image
2)、有时候 A 与 B 双向绑定,B 与 C 双向绑定,其实相当于 A、B、C 一起绑定在一条数据链Chain上,每当有一个in数据变动,发送新数据到C
hain上,再由Chain更新所有的out数据。
这样实现单向/双向数据流。
3)、利用KVO和UI(addTarget:)事件,数据链就相当于Obverse,每个Observer用一个ChainCode标记,Observer察看每个in数据变动,并更新到所有out数据。
4)、次要对外接口论述
链式语法调用的API,必须以 _inout 或 _in 结尾(必定要有数据in起源,不然后续也没意义),前面的绑定程序可随便,不影响绑定后果。
- _inout 发送+接收数据
- _in 只发送数据
- _out 只接收数据
- _cv 进行自定义数据转换后再返回
- _filter 条件过滤
- _out_key_any 绑定自定义事件
- _out_not 接管的数据取反再返回
具体接口如下:
#pragma mark - 双向绑定+ (DataBindBlock)_inout;+ (DataBindUIBlock)_inout_ui;+ (DataBindConvertBlock)_inout_cv;+ (DataBindUIConvertBlock)_inout_ui_cv;- (DataBindBlock)_inout;- (DataBindUIBlock)_inout_ui;- (DataBindConvertBlock)_inout_cv;- (DataBindUIConvertBlock)_inout_ui_cv;#pragma mark - 单向绑定-发送(数据更新,只发送新数据,不接管)+ (DataBindBlock)_in;+ (DataBindUIBlock)_in_ui;- (DataBindBlock)_in;- (DataBindUIBlock)_in_ui;#pragma mark - 单向绑定-接管(数据更新,只接管新数据,不发送)- (DataBindBlock)_out;- (DataBindConvertBlock)_out_cv;- (DataBindBlock)_out_not;- (DataBindKeyAnyOutBlock)_out_key_any;#pragma mark - 过滤- (DataBindFilterBlock)_fil
四、应用示例
设置数据绑定个别放在胶水层(ViewController)中进行,具体可联合本身设计模式灵活运用。
导入头文件:
#import "CRDataBind.h"
进行单向/双向数据绑定(label-只接收数据,model-即发送也接收数据响应):
/**绑定 model.winRate <---> rateLb.text <---> rateSlider.value */ CRDataBind ._inout(self.lotteryVM.currentLottery, @"winRate") ._inout_ui(self.rateSlider, @"value", UIControlEventValueChanged) ._out(self.rateLb, @"text");
依据条件过滤,未达到条件不解决响应:
/**绑定 model.winCode <---> winCodeLb.text <---> winCodeTF.text 减少fiter过滤中奖号大于3位数的响应 */ CRDataBind ._inout(self.lotteryVM.currentLottery, @"winCode") ._out(self.winCodeLb, @"text") ._inout_ui(self.winCodeTF, @"text", UIControlEventEditingChanged) ._filter(^BOOL(NSString *text) { // 减少过滤:中奖号码不超过3位数,界面上无奈配置大于3位数 return text.length <= 3; });
自定义数据接管格局:
/**绑定 model.sn <---> snLb.text <---> self.view.backgroundColor 其中backgroundColor须要转换输入格局 */ CRDataBind ._inout(self.lotteryVM.currentLottery, @"sn") ._out(self.snLb, @"text") ._out_cv(self.view, @"backgroundColor", ^UIColor *(NSNumber *num) { NSInteger index = num.integerValue % kBGColors.count; return kBGColors[index]; });
绑定自定义事件:
/**绑定 model.isWin <---> isWinLb.text <---> self.isWin 减少内部自定义事件,中奖后让抽奖号码闪动 */ __weak __typeof(&*self) weakSelf = self; CRDataBind ._inout(self.lotteryVM.currentLottery, @"isWin") ._out(self.isWinLb, @"text") ._out_key_any(@"202122", ^(NSNumber *num) { weakSelf.isWin = num.boolValue; NSLog(@">>>在setIsWin:中触发中奖时号码闪动,iswin = %d", weakSelf.isWin); });
五、功效举证
针对本案制作了CRDataBindDemo,它是一个老虎机摇号抽奖程序,通过MVVM + CRDataBind链式响应编程,疾速地实现了多个带界面交互的数据绑定业务。
1、demo成果
地址:github.com/stkusegithu…
次要数据绑定链有:
- model.sn <---> snLb.text <---> self.view.backgroundColor(期号递增显示不同背景色)
- model.winRate <---> rateLb.text <---> rateSlider.value(滑动slider扭转中奖率)
- model.code <---> codeLb.text(抽奖后显示抽奖号码变动)
- model.winCode <---> winCodeLb.text <---> winCodeTF.text(设置下一期中奖号)
- model.isWin <---> isWinLb.text <---> self.isWin(显示开释中奖,播放数字闪动动画)
image
image
2、功效阐明
比方demo中,须要配置老虎机下一期中奖号码时,在未应用CRDataBind前的业务代码书写如下:
- (void)setupBind { // 绑定textField编辑事件 [self.winCodeTF addTarget:self action:@selector(winCodeTFdidEdittingChanged:) forControlEvents:UIControlEventEditingChanged]; // 未知的中央调用 self.lotteryVM.currentLottery.winCode = @"222"; [self freshWinCodeUI];}- (void)winCodeTFdidEdittingChanged:(UITextField *)textField { if (textField.text.length > 3) { textField.text = [textField.text substringToIndex:3]; return; } self.lotteryVM.currentLottery.winCode = self.winCodeLb.text = textField.text;}- (void)freshWinCodeUI { // 刷新界面 NSString *winCode = self.lotteryVM.currentLottery.winCode; self.winCodeLb.text = self.winCodeTF.text = winCode;
能够看出下面比拟零散和繁琐。再看看当咱们应用CRDataBind后,是不是变得洁净清新多了:
- (void)setupBind { /**绑定 model.winCode <---> winCodeLb.text <---> winCodeTF.text 减少fiter过滤中奖号大于3位数的响应 */ CRDataBind ._inout(self.lotteryVM.currentLottery, @"winCode") ._out(self.winCodeLb, @"text") ._inout_ui(self.winCodeTF, @"text", UIControlEventEditingChanged) ._filter(^BOOL(NSString *text) { // 过滤:中奖号码需小于3位数 return text.length <= 3; });}
六、外围代码范畴
代码位于目录 CRDataBindDemo/CRDataBindDemo/CRDataBind/下
---CRDataBind
+---CRDataBindDefine.h
+---CRDataBind.h
+---CRDataBind.m
+---CRDataBindObserverManager.h
+---CRDataBindObserverManager.m
+---CRDataBindObserver.h
+---CRDataBindObserver.m
+---NSObject+DataBind.h
+---NSObject+DataBind.m
+---CRDataBindObserverModel.h
+---CRDataBindObserverModel.m
+---CRDataBindTargetModel.h
+---CRDataBindTargetModel.m
举荐文章
iOS 架构
iOS获取调用链
iOS架构模式(代理,block,告诉,MVC,MVP,MVVM)
视频材料
iOS_架构模式
如果您感觉还不错,麻烦在文末 “点个赞” 或者 下方评论 ,谢谢您的反对
查看原文