RAC 之響應式編程
RAC之響應式編程
開篇扯淡
在項目開發過程當中,一般我們看到生活中網頁當中更多響應式編程的栗子。響應式布局,自動校驗email,自動校驗數據等等都是響應式編程的一部分。小編最近剛剛接AngularJS.里面自動校驗的栗子如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src=";
</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>
那么IOS開發當中當然也有類似的框架來做。小編暫且講一下ReactiveCocoa屌炸天的一個框架。順便說一下用處吧。
ReactiveCocoa原理
ReactiveCocoa其實總得來說是信號機制,一般我們在做項目開發當中,會處理n多事件響應,簡單的按鈕點擊,視圖滑動,網絡請求,視圖刷新等等,一般我們在處理這些事件的時候,往往采用的開發模式是Delegate,Notification,Block,KVO等,單單小項目還Ok.一旦項目變大。會發現你的項目狀態變量處理也越來越多。項目也越來越復雜。抓比了吧。。。
信號&&訂閱者
ReactiveCocoa綜合了Delegate,Notification,Block,KVO等對于RAC來說。主要是信號和訂閱者機制,例如點擊一個按鈕,產生一個signal,然后被Subscriber訂閱后,可以響應相應的事件。 limboy 有個很形象的比喻就是每個signal好比一個插頭,Subscriber好比插座,插頭可以插在任意的插座上,也就是signal可以被多個Subscriber訂閱,但是只有訂閱后,才會響應。未被訂閱時,為冷信號。訂閱之后,成為熱信號。
傳統方式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>
接下來你肯定會罵小編。尼瑪。你不是說的很刁很方便嘛。怎么會那么多行。? 哈哈哈 這只是剛起步。。。走起來就會簡單很多了。。。 然后就是RAC做動態檢查。 這時候應該用到UITextField
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>
應用環境是,當當前用戶名和密碼同時輸入了數據的時候,用戶登錄的按鈕才可以點擊,這時候用到了信號的組合。combineLatest(信號聯合)。
combineLatest:@[
self.usernameTextField.rac_textSignal,
self.passwordTextField.rac_textSignal
]
把usernameTextField的信號和passwordTextField的信號組合起來。
</code></pre>
reduce:^(NSString username, NSString password) {
return @(username.length > 0 && password.length > 0;
}];
通過上一步產生信號的usernameTextField,passwordTextField獲取其中輸入的值,然后判斷當前的輸入是否合法。
</code></pre>
RAC(self.logInButton, enabled)接受返回值。判斷當前按鈕是否可以被點擊。這樣是不是明朗了很多。如果采用常規的做法。需要遵循響應的代理,然后從代理中獲取當前的值。每當textField的值改變的時候做一次判斷。同時當前textField有兩個。需要用tag來區分。這樣看起來。是不是簡單了很多。 然后我們看一下RAC的源文件
├── 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
剛剛用到的是UITextField。打開看一下UITextField.
- (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>
defer是RACSignal的一個類方法。返回的一個延遲的信號。如果不被訂閱,就是冷信號。訂閱則成為熱信號。
concat連接的是UITextFiled的UIControlEventAllEditingEvents信號。
map是把當前信號映射成為x.text。
takeUntil。看名字大概能看懂,就是在某個事件之前一直獲取當前信號。rac_willDeallocSignal。。意思是在UITextField銷毀之前一直獲取當前輸入信號。 這樣來分析UITextField,大概就會明朗了很多。
信號使用場景一。
例如我們在網絡上獲取一列數據,而后我們需要展示在cell上。what should i do ? RAC_GET如下
@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>
[self GET:urlString parameters:params responseKeys:nil autoRun:YES progress:nil completion:^(BOOL success, id userinfo)
調用的是 GFHTTPManger 網絡管理類下的一個GET請求方法。
[subscriber sendNext:userinfo];
[subscriber sendCompleted];
將當前信號轉化為熱信號。發送給訂閱者。userinfo是當前用戶請求成功之后獲取到的數據。做為參數傳遞給訂閱者。
然后寫好了接口類。如何調用呢。
@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>
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>
filter是過濾當前信號 return [[dictTmp objectForKey:@"status"] isEqualToString:@"success"]; 當當前status 字段為success時,當前請求成功,否則失敗。失敗后不再進行下一步。
map 映射當前信號。 網絡請求過來的數據是json類型,不過我們需要的是模型數組。然后在map中。我們格式化當前json數據。
映射操作完成之后,subscribeNext執行應設置后的操作。
來自: https://github.com/mgoofyy/RacExample