之前講了RAC如何幫我們實現KVO / 代理 / 事件 / 通知
今天先不去分析它的核心代碼, 我們先看看ReactiveObjC庫里面一些特別的東西, 如果大家點開ReactiveObjC目錄應該會看到很多category, 今天我們先來看看這些
我們先從UITextView+RACSignalSupport.h開始看
#import <UIKit/UIKit.h> @class RACDelegateProxy; @class RACSignal<__covariant ValueType>; NS_ASSUME_NONNULL_BEGIN @interface UITextView (RACSignalSupport) @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; - (RACSignal<NSString *> *)rac_textSignal; @end
這里有一個屬性跟一個方法,
關於RACDelegateProxy這個類的用途大概是把初始化傳入的代理綁定或者添加給當前正在處理的信號
給大家一個例子:
#import "ViewController.h" #import <ReactiveObjC.h> #import <objc/runtime.h> @interface ViewController ()<UITextViewDelegate> @end @implementation ViewController - (void)viewDidLoad {; [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 以UITextViewDelegate來初始化一個RACDelegateProxy RACDelegateProxy *delegateProxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; // 注冊要實現的方法 [[delegateProxy rac_signalForSelector:@selector(textViewDidBeginEditing:)] subscribeNext:^(RACTuple * _Nullable x) { NSLog(@"開始編輯"); }]; UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
textView.center = self.view.center; textView.backgroundColor = [UIColor greenColor]; // 設置代理為我們創建的RACDelegateProxy, 注意要轉義不然會有警告 textView.delegate = (id<UITextViewDelegate>)delegateProxy; [self.view addSubview:textView]; // retain我們創建的delegateProxy, 避免被釋放 objc_setAssociatedObject(textView, _cmd, delegateProxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
這個一般是RAC內部使用, 我們比較少用. 另外也只能處理沒有返回值的代理方法
可以到UITextview+RACSignalSupport.m里面看看, 也是類似這樣用的
- (RACDelegateProxy *)rac_delegateProxy { RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd); if (proxy == nil) { proxy = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UITextViewDelegate)]; objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return proxy; }
好了, 現在我們來使用下這個UITextView類別唯一的方法
- (RACSignal<NSString *> *)rac_textSignal;
大家可以看到, 這個方法會返回一個信號 我們可以對他訂閱, 試試看
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; textView.center = self.view.center; textView.backgroundColor = [UIColor greenColor]; [self.view addSubview:textView]; [[textView rac_textSignal] subscribeNext:^(NSString * _Nullable x) { NSLog(@"%@", x); }];
運行看看, 當我們在textView中輸入文字的時候會打印:
2017-07-23 22:41:42.841 RAC[70053:14036438] 1 2017-07-23 22:41:43.353 RAC[70053:14036438] 11 2017-07-23 22:41:44.031 RAC[70053:14036438] 111
所以這個x就是Textview的內容了.
下面我們看看
UITextField+RACSignalSupport.h
#import <UIKit/UIKit.h> @class RACChannelTerminal<ValueType>; @class RACSignal<__covariant ValueType>; NS_ASSUME_NONNULL_BEGIN @interface UITextField (RACSignalSupport) - (RACSignal<NSString *> *)rac_textSignal; - (RACChannelTerminal<NSString *> *)rac_newTextChannel; @end NS_ASSUME_NONNULL_END
這里有兩個方法, 我們先看第一個rac_textSignal
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 35)]; textField.center = self.view.center; textField.backgroundColor = [UIColor yellowColor]; [[textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) { NSLog(@"%@", x); }]; [self.view addSubview:textField];
運行看看, 我們輸入數字的時候會打印內容:
2017-07-23 22:55:45.686 RAC[70205:14118946] 1 2017-07-23 22:55:46.139 RAC[70205:14118946] 11 2017-07-23 22:55:46.798 RAC[70205:14118946] 111
然后我們看看另外一個方法
- (RACChannelTerminal<NSString *> *)rac_newTextChannel;
這里涉及到了一個類RACChannelTerminal, 我們點進去看看這個類
@interface RACChannelTerminal<ValueType> : RACSignal<ValueType> <RACSubscriber> - (instancetype)init __attribute__((unavailable("Instantiate a RACChannel instead"))); // Redeclaration of the RACSubscriber method. Made in order to specify a generic type. - (void)sendNext:(nullable ValueType)value; @end
可以看到它是一個RACSignal的子類, 我們先調用看看這個方法
[[textField rac_newTextChannel] subscribeNext:^(NSString * _Nullable x) { NSLog(@"%@", x); }];
效果跟rac_textSignal一樣, 那么它有什么特別的用法呢
它的作用是做雙向綁定 關於什么是雙向綁定呢? 給大家一個簡單的例子:
UITextField *textFieldA = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 35)]; textFieldA.center = self.view.center; textFieldA.backgroundColor = [UIColor yellowColor]; [self.view addSubview:textFieldA]; UITextField *textFieldB = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 35)]; textFieldB.center = CGPointMake(self.view.center.x, self.view.center.y + 50); textFieldB.backgroundColor = [UIColor yellowColor]; [self.view addSubview:textFieldB]; RACChannelTerminal *terminalA = [textFieldA rac_newTextChannel]; RACChannelTerminal *terminalB = [textFieldB rac_newTextChannel]; [terminalA subscribe:terminalB]; [terminalB subscribe:terminalA];
運行可以看到, 改變textFieldA的值textFieldB的值也會跟着改變, 反過來也一樣.
這里如果要實現雙向綁定, 其實還有一個簡單的方法:
RACChannelTo(textFieldA, text) = RACChannelTo(textFieldB, text);
大家可以試試看.
如果我們不僅僅想讓兩個綁定對象之間的值簡單的相等而已呢? 比如textFieldA的值是123的時候textFieldB的值要為321要怎么處理呢?
這里我們先說一個一會用到的方法: map
map方法,將會創建一個和原來一模一樣的信號,只不過新的信號傳遞的值變為了block(value)。
[[[textField rac_textSignal] map:^id _Nullable(NSString * _Nullable value) { if ([value isEqualToString:@"11"]) { return @"1"; } else { return @"0"; } }] subscribeNext:^(id _Nullable x) { NSLog(@"%@", x); }];
運行看看, 當我們輸入1, 會打印0, 輸入11的時候會打印1, 這里就是把傳遞的值從textField的text轉變成為我們的1 和 0;
然后有個特別的地方, 加入我們知道傳遞的值的類型, 我們就可以直接把后面訂閱的block里面的參數類型直接改成我們知道的類型
例如把id改為NSString *運行結果也是一樣的, 這個是RAC一個比較特別的地方
那么要實現上面的123 到 321可以這樣寫:
RACChannelTerminal *terminalA = [textFieldA rac_newTextChannel]; RACChannelTerminal *terminalB = [textFieldB rac_newTextChannel]; [[terminalA map:^id _Nullable(id _Nullable value) { if ([value isEqualToString:@"123"]) { return @"321"; } return value; }] subscribe:terminalB]; [terminalB subscribe:terminalA];
大家可以自己運行看看效果, 當textFieldA輸入123的時候textFieldB會變為321
下面我們看看
UIActionSheet+RACSignalSupport.h
#import <UIKit/UIKit.h> @class RACDelegateProxy; @class RACSignal<__covariant ValueType>; NS_ASSUME_NONNULL_BEGIN @interface UIActionSheet (RACSignalSupport) @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; - (RACSignal<NSNumber *> *)rac_buttonClickedSignal; @end
rac_delegateProxy跟之前textview是一樣的用法這里開始就不再解釋這類屬性了
我們直接試着使用rac_buttonClickedSignal
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"RAC ActionSheet" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"OK" otherButtonTitles:@"Other", nil]; [[actionSheet rac_buttonClickedSignal] subscribeNext:^(NSNumber * _Nullable x) { NSLog(@"%@", x); }]; [actionSheet showInView:self.view];
運行看看, x是actionSheet上按鈕的編號, 我們拿到編號就可以做響應的事件處理了.
UIAlertView+RACSignalSupport.h
@interface UIAlertView (RACSignalSupport) @property (nonatomic, strong, readonly) RACDelegateProxy *rac_delegateProxy; - (RACSignal<NSNumber *> *)rac_buttonClickedSignal; - (RACSignal<NSNumber *> *)rac_willDismissSignal; @end NS_ASSUME_NONNULL_END
它有兩個方法, 一個是點擊的時候用, 一個是dismiss的時候用
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"RAC" message:@"RAC Alert" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil]; [[alert rac_buttonClickedSignal] subscribeNext:^(NSNumber * _Nullable x) { NSLog(@"click: x"); }]; [[alert rac_willDismissSignal] subscribeNext:^(NSNumber * _Nullable x) { NSLog(@"dismiss: %@", x); }]; [alert show];
UIControl+RACSignalSupport.h
#import <UIKit/UIKit.h> @class RACSignal<__covariant ValueType>; NS_ASSUME_NONNULL_BEGIN @interface UIControl (RACSignalSupport) - (RACSignal<__kindof UIControl *> *)rac_signalForControlEvents:(UIControlEvents)controlEvents; @end NS_ASSUME_NONNULL_END
只有一個方法, 這個之前講過是做UIControllerEvent處理的, 再給個例子:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setFrame:CGRectMake(0, 0, 100, 35)]; [button setCenter:self.view.center]; [button setBackgroundColor:[UIColor yellowColor]]; [button setTitle:@"按鈕" forState:UIControlStateNormal]; [[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { NSLog(@"點擊了按鈕"); x.backgroundColor = [UIColor redColor]; }]; [self.view addSubview:button];
UIDatePicker+RACSignalSupport.h
#import <UIKit/UIKit.h> @class RACChannelTerminal<ValueType>; NS_ASSUME_NONNULL_BEGIN @interface UIDatePicker (RACSignalSupport) - (RACChannelTerminal<NSDate *> *)rac_newDateChannelWithNilValue:(nullable NSDate *)nilValue; @end NS_ASSUME_NONNULL_END
它只有一個綁定的方法, 直接給大家一個例子:
大概效果為我們在Controller中添加一個UITextField跟一個UIDatePicker, 然后獲取他們的RACChannelTerminal,
將UIDatePicker綁定給UITextField, 當我們滾動datePicker的時候 textField的值會跟着改變
UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 160)]; datePicker.center = self.view.center; datePicker.backgroundColor = [UIColor redColor]; [self.view addSubview:datePicker]; UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 180, 35)]; textField.center = CGPointMake(self.view.center.x, self.view.center.y - 100); textField.backgroundColor = [UIColor yellowColor]; [self.view addSubview:textField]; RACChannelTerminal *datePickerTerminal = [datePicker rac_newDateChannelWithNilValue:[NSDate date]]; RACChannelTerminal *textFieldTerminal = [textField rac_newTextChannel]; [[datePickerTerminal map:^id _Nullable(id _Nullable value) { NSLog(@"%@", value); NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; return [dateFormatter stringFromDate:value]; }] subscribe:textFieldTerminal];
運行截圖:
這篇先寫到這里, 下次我們再接着看