RAC 之响应式编程
taotao28
8年前
<h2>RAC之响应式编程</h2> <h2>开篇扯淡</h2> <p>在项目开发过程当中,一般我们看到生活中网页当中更多响应式编程的栗子。响应式布局,自动校验email,自动校验数据等等都是响应式编程的一部分。小编最近刚刚接AngularJS.里面自动校验的栗子如下</p> <pre> <code class="language-html"><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body> <div data-ng-app="" data-ng-init="quantity=1;price=5"> <h2>价格计算器</h2> 数量: <input type="number" ng-model="quantity"> 价格: <input type="number" ng-model="price"> <p><b>总价:</b> {{quantity * price}}</p> </div> </body> </html> </code></pre> <p>那么IOS开发当中当然也有类似的框架来做。小编暂且讲一下ReactiveCocoa屌炸天的一个框架。顺便说一下用处吧。</p> <h2>ReactiveCocoa原理</h2> <p>ReactiveCocoa其实总得来说是信号机制,一般我们在做项目开发当中,会处理n多事件响应,简单的按钮点击,视图滑动,网络请求,视图刷新等等,一般我们在处理这些事件的时候,往往采用的开发模式是Delegate,Notification,Block,KVO等,单单小项目还Ok.一旦项目变大。会发现你的项目状态变量处理也越来越多。项目也越来越复杂。抓比了吧。。。</p> <h2>信号&&订阅者</h2> <p>ReactiveCocoa综合了Delegate,Notification,Block,KVO等对于RAC来说。主要是信号和订阅者机制,例如点击一个按钮,产生一个signal,然后被Subscriber订阅后,可以响应相应的事件。 <a href="/misc/goto?guid=4959674626352246664" rel="nofollow,noindex">limboy</a> 有个很形象的比喻就是每个signal好比一个插头,Subscriber好比插座,插头可以插在任意的插座上,也就是signal可以被多个Subscriber订阅,但是只有订阅后,才会响应。未被订阅时,为冷信号。订阅之后,成为热信号。</p> <pre> <code class="language-objectivec">传统方式UIButton UIButton *myButton = [[UIButton alloc] init...]; [myButton addTarget:something action:@selector(myAction) forControlEvents:UIControlEventTouchUpInside]; RAC方式 @property (nonatomic, strong ) RACCommand *commandDelete; @property (nonatomic, weak) IBOutlet UIButton *deleteButton; self.deleteButton.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [RACSignal return:input]; }]; - (RACCommand *)commandDelete { if (!_commandDelete) { _commandDelete = self.deleteButton.rac_command; } return _commandDelete; } </code></pre> <p>接下来你肯定会骂小编。尼玛。你不是说的很刁很方便嘛。怎么会那么多行。? 哈哈哈 这只是刚起步。。。走起来就会简单很多了。。。 然后就是RAC做动态检查。 这时候应该用到UITextField</p> <pre> <code class="language-html">RAC(self.logInButton, enabled) = [RACSignal combineLatest:@[ self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal ] reduce:^(NSString *username, NSString *password) { return @(username.length > 0 && password.length > 0; }]; </code></pre> <p>应用环境是,当当前用户名和密码同时输入了数据的时候,用户登录的按钮才可以点击,这时候用到了信号的组合。combineLatest(信号联合)。</p> <pre> <code class="language-html">combineLatest:@[ self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal ] 把usernameTextField的信号和passwordTextField的信号组合起来。 </code></pre> <pre> <code class="language-html">reduce:^(NSString *username, NSString *password) { return @(username.length > 0 && password.length > 0; }]; 通过上一步产生信号的usernameTextField,passwordTextField获取其中输入的值,然后判断当前的输入是否合法。 </code></pre> <p>RAC(self.logInButton, enabled)接受返回值。判断当前按钮是否可以被点击。这样是不是明朗了很多。如果采用常规的做法。需要遵循响应的代理,然后从代理中获取当前的值。每当textField的值改变的时候做一次判断。同时当前textField有两个。需要用tag来区分。这样看起来。是不是简单了很多。 然后我们看一下RAC的源文件</p> <pre> <code class="language-html">├── UIActionSheet+RACSignalSupport.h ├── UIActionSheet+RACSignalSupport.m ├── UIAlertView+RACSignalSupport.h ├── UIAlertView+RACSignalSupport.m ├── UIBarButtonItem+RACCommandSupport.h ├── UIBarButtonItem+RACCommandSupport.m ├── UIButton+RACCommandSupport.h ├── UIButton+RACCommandSupport.m ├── UICollectionReusableView+RACSignalSupport.h ├── UICollectionReusableView+RACSignalSupport.m ├── UIControl+RACSignalSupport.h ├── UIControl+RACSignalSupport.m ├── UIControl+RACSignalSupportPrivate.h ├── UIControl+RACSignalSupportPrivate.m ├── UIDatePicker+RACSignalSupport.h ├── UIDatePicker+RACSignalSupport.m ├── UIGestureRecognizer+RACSignalSupport.h ├── UIGestureRecognizer+RACSignalSupport.m ├── UIImagePickerController+RACSignalSupport.h ├── UIImagePickerController+RACSignalSupport.m ├── UIRefreshControl+RACCommandSupport.h ├── UIRefreshControl+RACCommandSupport.m ├── UISegmentedControl+RACSignalSupport.h ├── UISegmentedControl+RACSignalSupport.m ├── UISlider+RACSignalSupport.h ├── UISlider+RACSignalSupport.m ├── UIStepper+RACSignalSupport.h ├── UIStepper+RACSignalSupport.m ├── UISwitch+RACSignalSupport.h ├── UISwitch+RACSignalSupport.m ├── UITableViewCell+RACSignalSupport.h ├── UITableViewCell+RACSignalSupport.m ├── UITableViewHeaderFooterView+RACSignalSupport.h ├── UITableViewHeaderFooterView+RACSignalSupport.m ├── UITextField+RACSignalSupport.h ├── UITextField+RACSignalSupport.m ├── UITextView+RACSignalSupport.h ├── UITextView+RACSignalSupport.m </code></pre> <p>刚刚用到的是UITextField。打开看一下UITextField.</p> <pre> <code class="language-html">- (RACSignal *)rac_textSignal { @weakify(self); return [[[[[RACSignal defer:^{ @strongify(self); return [RACSignal return:self]; }] concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]] map:^(UITextField *x) { return x.text; }] takeUntil:self.rac_willDeallocSignal] setNameWithFormat:@"%@ -rac_textSignal", self.rac_description]; } </code></pre> <p>defer是RACSignal的一个类方法。返回的一个延迟的信号。如果不被订阅,就是冷信号。订阅则成为热信号。</p> <p>concat连接的是UITextFiled的UIControlEventAllEditingEvents信号。</p> <p>map是把当前信号映射成为x.text。</p> <p>takeUntil。看名字大概能看懂,就是在某个事件之前一直获取当前信号。rac_willDeallocSignal。。意思是在UITextField销毁之前一直获取当前输入信号。 这样来分析UITextField,大概就会明朗了很多。</p> <p>信号使用场景一。</p> <p>例如我们在网络上获取一列数据,而后我们需要展示在cell上。what should i do ? RAC_GET如下</p> <pre> <code class="language-html">@implementation GFHTTPManger (Uniform) + (RACSignal *)rac_get:(NSString *)urlString params:(NSDictionary *)params { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSURLSessionTask *task = [self GET:urlString parameters:params responseKeys:nil autoRun:YES progress:nil completion:^(BOOL success, id userinfo) { [subscriber sendNext:userinfo]; [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }] replayLazily]; } @end </code></pre> <pre> <code class="language-html">[self GET:urlString parameters:params responseKeys:nil autoRun:YES progress:nil completion:^(BOOL success, id userinfo) 调用的是 GFHTTPManger 网络管理类下的一个GET请求方法。 </code></pre> <pre> <code class="language-html">[subscriber sendNext:userinfo]; [subscriber sendCompleted]; 将当前信号转化为热信号。发送给订阅者。userinfo是当前用户请求成功之后获取到的数据。做为参数传递给订阅者。 </code></pre> <p>然后写好了接口类。如何调用呢。</p> <pre> <code class="language-html">@interface WeiBoContentViewModel() @property (nonatomic, readwrite) BOOL shouldReloadData; @property (nonatomic, strong, readwrite) NSArray *weiboArray; @property (nonatomic, strong, readwrite) NSArray *weiboCommentArray; @end @implementation WeiBoContentViewModel - (void)requestRemoteWeiBo { @weakify(self) [[[[GFHTTPManger rac_get:@"/weibo/Weiboc/getWeiboList" params:nil] filter:^BOOL(NSDictionary *value) { NSDictionary *dictTmp = [value objectForKey:@"response"]; return [[dictTmp objectForKey:@"status"] isEqualToString:@"success"]; }] map:^id(NSDictionary *value) { @strongify(self) NSDictionary *responseDict = [value objectForKey:@"response"]; NSDictionary *dataDict = [responseDict objectForKey:@"data"]; NSArray *notificationArray = [dataDict objectForKey:@"WeiboListInfo"]; return [self formatData:notificationArray]; //格式化当前json转化为模型数组。 }] subscribeNext:^(NSArray *x) { @strongify(self) self.weiboArray = [x copy]; self.shouldReloadData = x.count; }]; } </code></pre> <pre> <code class="language-html">json格式如下 "response": { "status": "success", "code": 1, "data": { "WeiboListInfo": [ { "id": "24", "uid": "-1", "content": "Hello ", "comment_count": "0", "repost_count": "0", "create_time": "1465727950", "WeiboCommentList": { "WeiboCommentListInfo": [ { "id": "8", "uid": "-1", "weibo_id": "24", "content": "Hello", "create_time": "1465781699", "status": "1", "to_comment_id": "0" }, { "id": "7", "uid": "-1", "weibo_id": "24", "content": "hello", "create_time": "1465727958", "status": "1", "to_comment_id": "0" } ] } }, </code></pre> <p>filter是过滤当前信号 return [[dictTmp objectForKey:@"status"] isEqualToString:@"success"]; 当当前status 字段为success时,当前请求成功,否则失败。失败后不再进行下一步。</p> <p>map 映射当前信号。 网络请求过来的数据是json类型,不过我们需要的是模型数组。然后在map中。我们格式化当前json数据。</p> <p>映射操作完成之后,subscribeNext执行应设置后的操作。</p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959674626436645672" rel="nofollow">https://github.com/mgoofyy/RacExample</a></p> <p> </p>