什么是RAC?
幾乎每一篇介紹RAC的文章開頭都是這么一個問題。我這篇文章是寫給新手(包括我自己)看的,所以這個問題更是無法忽視。
簡單的說,RAC就是一個第三方庫,他可以大大簡化你的代碼過程。
官方的說,ReactiveCocoa(其簡稱為RAC)是由GitHub開源的一個應用於iOS和OS X開發的新框架。RAC具有函數式編程和響應式編程的特性。
為什么我們要學習RAC?
為了提高我們的開發效率。RAC在某些特定情況下開發時可以大大簡化代碼,並且目前來看安全可靠。
配置RAC環境
我習慣用cocoapods來安裝github上得開源庫,不會的新手iOS開發者有興趣可以去學一下。
想學習cocoapods的同學推薦唐巧前輩的文章。
1
2
|
platform:ios,
'8.0'
pod
'ReactiveCocoa'
,
'~>2.1.8'
|
這里有一點要注意下就是RAC的版本問題,由於還沒學習Swift,所以我是用OC編寫程序的,最新版的RAC已經支持Swift了,但是在OC的程序安裝最新版的RAC可能跑不起來,所以推薦大家使用2.5.0版本以下的RAC(具體支持Swift的版本可能有誤,但我引用的2.1.8肯定是沒問題的)。
使用RAC
1.target-action
RAC最基本的入門使用技巧就是對事件的監聽。
PS:在iOS開發中,我們所說的點擊事件其實就是target-action,接觸過iOS開發的人都不會陌生UIControlEventTouchUpInside,這就是按下並松開的動作。不僅僅是UIButton,還有UITextField也有目標-動作模式。
使用前別忘了引用頭文件~
#import <ReactiveCocoa/ReactiveCocoa.h>
接下來就是最關鍵的RAC代碼了。
[[self.textFild rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x){ NSLog(@"change"); }];
就這么短短的兩行代碼。他實現了一個功能,即監聽了textFild的UIControlEventEditingChanged事件,當事件發生時實現方法NSLog
。
所以我們就可以以這段代碼為模板進行RAC的使用,舉一反三,以后的UIButton點擊事件我們都可以用RAC方法進行添加,再也不用add Target
了。
對於textFild的文字更改監聽也有更簡單的寫法
[[self.textFild rac_textSignal] subscribeNext:^(id x) { NSLog(@"%@",x); }];
這樣就是每次改變TextFild都輸出改變后的結果。
再比如給我們的某個label添加一個手勢動作,我們也可以用簡單的RAC代碼完成
1
2
3
4
5
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[[tap rac_gestureSignal] subscribeNext:^(
id
x) {
NSLog
(@
"tap"
);
}];
[
self
.view addGestureRecognizer:tap];
|
這段具體我就不解釋了,相信大家都能看得懂,看不懂的自己寫寫就懂了。
2.代理
用RAC寫代理是有局限的,它只能實現返回值為void的代理方法
首先我們要明白我們為什么要用RAC寫代理?答:簡化代碼!是的,的確為了簡化代碼,為什么我要再這里強調這個,是因為在代理方法中我深深的感受到了RAC的優點。一開始我也不願意花功夫去學RAC,但是我師父給我舉了一個例子,如果一個View里有多個AlertView,每個AlertView有很多個按鈕,每個按鈕都有自己的點擊事件,我應該怎么寫?我想了一下,不但每個按鈕需要打標記,而且每個AlertView也要打標記,然后再往代理點擊事件里加各種方法,代碼就又臭又長。那么讓我們看看RAC怎么寫代理方法。
1
2
3
4
5
6
7
|
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@
"RAC"
message:@
"RAC TEST"
delegate:
self
cancelButtonTitle:@
"cancel"
otherButtonTitles:@
"other"
,
nil
];
[[
self
rac_signalForSelector:
@selector
(alertView:clickedButtonAtIndex:) fromProtocol:
@protocol
(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) {
NSLog
(@
"%@"
,tuple.first);
NSLog
(@
"%@"
,tuple.second);
NSLog
(@
"%@"
,tuple.third);
}];
[alertView show];
|
我們來看RAC的語句。@selector
是指這次事件監聽的方法fromProtocol
指依賴的代理。這里block中有一個RACTuple,他相當於是一個集合類,他下面的first,second等就是類的各個參數,我這里點了AlertView第二個按鈕other輸出了一下。
1
2
3
|
2016-01-04 18:24:29.114 RACStudyTest[5003:388870] <UIAlertView: 0x7ff260c90c70; frame = (0 0; 0 0); layer = <CALayer: 0x7ff260c91030>>
2016-01-04 18:24:29.115 RACStudyTest[5003:388870] 1
2016-01-04 18:24:29.115 RACStudyTest[5003:388870] (null)
|
可以看出tuple.second
是ButtonAtIndex中Button的序號。那么對於上面那個我舉的例子,就可以用switch
給各個按鈕添加方法,這樣的代碼看起來更容易理解,方面后期維護。
當然了,AlertView代理也有簡化的代碼。
1
2
3
|
[[alertView rac_buttonClickedSignal] subscribeNext:^(
id
x) {
NSLog
(@
"%@"
,x);
}];
|
這里的x就是各個Button的序號了,可以直接應對我上述遇到的問題。
3.通知
在我們的開發中通知也是一個比較常用的功能,主要的應用場景是某個頁面進行數據重傳需要更新model但是點擊返回棧時不會刷新返回界面的數據,這時就可以用通知來更新另一個頁面的數據,當然我們也可以在另一個頁面的ViewDidAppear
方法中刷新數據,但那是題外話。
這里寫的Demo就是我上述說的情況。
首先,在某個頁面中我們需要發出通知,這里就是最基本的通知的寫法。發送名為postdata的通知並傳送一個數組dataArray。
1
2
|
NSMutableArray
*dataArray = [[
NSMutableArray
alloc] initWithObjects:@
"1"
, @
"2"
, @
"3"
,
nil
];
[[
NSNotificationCenter
defaultCenter] postNotificationName:@
"postData"
object:dataArray];
|
而在接受的頁面我們需要增加觀察者並接受數組,這時我們的RAC就派上用場了。
1
2
3
4
|
[[[
NSNotificationCenter
defaultCenter] rac_addObserverForName:@
"postData"
object:
nil
] subscribeNext:^(
NSNotification
*notification) {
NSLog
(@
"%@"
, notification.name);
NSLog
(@
"%@"
, notification.object);
}];
|
當這個頁面監聽到名為postdata的通知時他就會執行block中的方法,當然這里的參數改成id x
也是可以的,這里用NSNotification主要是強調它的類型。讓我們看看控制台的輸出。
1
2
3
4
5
6
|
2016-01-04 20:10:52.274 RACStudyTest[5918:439077] postData
2016-01-04 20:10:52.275 RACStudyTest[5918:439077] (
1,
2,
3
)
|
可見,notification.object就是我們想要的數組,當然我們也可以傳一些model。值得一提的是,RAC中的通知不需要remove observer
,因為在rac_add方法中他已經寫了remove。
4.KVO
RAC中得KVO大部分都是宏定義,所以代碼異常簡潔,簡單來說就是RACObserve(TARGET, KEYPATH)
這種形式,TARGET是監聽目標,KEYPATH是要觀察的屬性值,這里舉一個很簡單的例子,如果UIScrollView滾動則輸出success。
1
2
3
4
5
6
7
|
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 400)];
scrolView.contentSize = CGSizeMake(200, 800);
scrolView.backgroundColor = [UIColor greenColor];
[
self
.view addSubview:scrolView];
[RACObserve(scrolView, contentOffset) subscribeNext:^(
id
x) {
NSLog
(@
"success"
);
}];
|
1.遍歷數組
NSArray
*numbers = @[
@1
,
@2
,
@3
,
@4
];
// 這里其實是三步
// 第一步: 把數組轉換成集合RACSequence numbers.rac_sequence
// 第二步: 把集合RACSequence轉換RACSignal信號類,numbers.rac_sequence.signal
// 第三步: 訂閱信號,激活信號,會自動把集合中的所有值,遍歷出來。
[numbers.rac_sequence.signal subscribeNext:^(
id
x) {
NSLog
(
@"%@"
,x);
}];
// 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組對象)
NSDictionary
*dict = @{
@"name"
:
@"xmg"
,
@"age"
:
@18
};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元組,會把元組的值,按順序給參數里面的變量賦值
RACTupleUnpack(
NSString
*key,
NSString
*value) = x;
// 相當於以下寫法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog
(
@"%@ %@"
,key,value);
}];
// 3.字典轉模型
// 3.1 OC寫法
NSString
*filePath = [[
NSBundle
mainBundle] pathForResource:
@"flags.plist"
ofType:
nil
];
NSArray
*dictArr = [
NSArray
arrayWithContentsOfFile:filePath];
NSMutableArray
*items = [
NSMutableArray
array];
for
(
NSDictionary
*dict in dictArr) {
FlagItem *item = [FlagItem flagWithDict:dict];
[items addObject:item];
}
// 3.2 RAC寫法
NSString
*filePath = [[
NSBundle
mainBundle] pathForResource:
@"flags.plist"
ofType:
nil
];
NSArray
*dictArr = [
NSArray
arrayWithContentsOfFile:filePath];
NSMutableArray
*flags = [
NSMutableArray
array];
_flags = flags;
// rac_sequence注意點:調用subscribeNext,並不會馬上執行nextBlock,而是會等一會。
[dictArr.rac_sequence.signal subscribeNext:^(
id
x) {
// 運用RAC遍歷字典,x:字典
FlagItem *item = [FlagItem flagWithDict:x];
[flags addObject:item];
}];
NSLog
(
@"%@"
,
NSStringFromCGRect
([UIScreen mainScreen].bounds));
// 3.3 RAC高級寫法:
NSString
*filePath = [[
NSBundle
mainBundle] pathForResource:
@"flags.plist"
ofType:
nil
];
NSArray
*dictArr = [
NSArray
arrayWithContentsOfFile:filePath];
// map:映射的意思,目的:把原始值value映射成一個新值
// array: 把集合轉換成數組
// 底層實現:當信號被訂閱,會遍歷集合中的原始值,映射成新值,並且保存到新的數組里。
NSArray
*flags = [[dictArr.rac_sequence map:^
id
(
id
value) {
return
[FlagItem flagWithDict:value];
}] array];