RAC 之響應式編程

taotao28 8年前發布 | 14K 次閱讀 iOS開發 移動開發

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

     

 本文由用戶 taotao28 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!