打算在項目中大面積使用RAC來開發(fā)毒费,所以整理一些常用的實踐范例和比較完整的api說明方便開發(fā)時隨時查閱
聲明式編程泛型Declarative programming
函數(shù)反應(yīng)式編程是聲明式編程的子編程范式之一
高階函數(shù)
需要滿足兩個條件
- 一個或者多個函數(shù)作為輸入。
- 有且僅有一個函數(shù)輸出蚀苛。
Objective-c里使用block作為函數(shù)
[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop)
{
NSLog(@"%@",number);
}];
映射map
NSArray * mappedArray = [array rx_mapWithBlock:^id(id each){
return @(pow([each integerValue],2));
}];
過濾filter
NSArray *filteredArray = [array rx_filterWithBlock:^BOOL(id each){
return ([each integerValue] % 2 == 0);
}]
折疊fold
[[array rx_mapWithBlock:^id (id each){
return [each stringValue];
}] rx_foldInitialValue:@"" block:^id (id memo , id each){
return [memo stringByAppendingString:each];
}];
Currying
用函數(shù)生成另一個函數(shù)
func filterGenerator(lastnameCondition: String) -> (Staff) -> (Bool) {
return {staff in
return staff.lastname == lastnameCondition
}
}
let filterWang = filterGenerator("Wang")
let filterHu = filterGenerator("Hu")
staffs.filter(filterHu)
RAC中使用高階函數(shù)
映射
NSArray *array = @[ @1, @2, @3 ];
RACSequence * stream = [array rac_sequence];
//RACSequence是一個RACStream的子類丙挽。
[stream map:^id (id value){
return @(pow([value integerValue], 2));
}];
//RACSequence有一個方法返回數(shù)組:array
NSLog(@"%@",[stream array]);
//避免污染變量的作用域
NSLog(@"%@",[[[array rac_sequence] map:^id (id value){
return @(pow([value integerValue], 2));
}] array]);
過濾
NSLog(@"%@", [[[array rac_sequence] filter:^BOOL (id value){
return [value integerValue] % 2 == 0;
}] array]);
折疊
NSLog(@"%@",[[[array rac_sequence] map:^id (id value){
return [value stringValue];
}] foldLeftWithStart:@"" reduce:^id (id accumulator, id value){
return [accumulator stringByAppendingString:value];
}]);
綁定鍵值
RACSignal * validEmailSignal = [self.textField.rac_textSignal map:^id (NSString *value){
return @([value rangeOfString:@"@"].location != NSNotFound);
}];
RAC(self.button, enabled) = validEmailSignal;
RAC(self.textField, textColor) = [validEmailSignal map: ^id (id value){
if([value boolValue]){
return [UIColor greenColor];
}else{
return [UIColor redColor];
}
}];
實踐
比較好的一個完整的RAC實踐的例子:https://github.com/ashfurrow/FunctionalReactivePixels
網(wǎng)絡(luò)請求生成對應(yīng)model
+ (RACSignal *)importPhotos{
RACReplaySubject * subject = [RACReplaySubject subject];
NSURLRequest * request = [self popularURLRequest];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
if (data) {
id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[subject sendNext:[[[results[@"photos"] rac_sequence] map:^id(NSDictionary *photoDictionary){
FRPPhotoModel * model = [FRPPhotoModel new];
[self configurePhotoModel:model withDictionary:photoDictionary];
[self downloadThumbnailForPhotoModel:model];
return model;
}] array]];
[subject sendCompleted];
}
else{
[subject sendError:connectionError];
}
}];
return subject;
}
過濾相同大小的圖片掖举,取出他們的url游盲,返回第一個
+ (NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSArray *)array{
return [[[[[array rac_sequence] filter:^ BOOL (NSDictionary * value){
return [value[@"size"] integerValue] == size;
}] map:^id (id value){
return value[@"url"];
}] array] firstObject];
}
觀察model里的圖片數(shù)據(jù)介袜,進(jìn)行為空過濾判斷,將data轉(zhuǎn)為UIImage唁影,再把綁定新信號的值給對象的關(guān)鍵路徑
- (void)setPhotoModel:(FRPPhotoModel *)photoModel{
self.subscription = [[[RACObserver(photoModel, thumbnailData)
filter:^ BOOL (id value){
return value != nil;
}] map:^id (id value){
return [UIImage imageWithData:value];
}] setKeyPath:@keypath(self.imageView, image) onObject:self.imageView];
}
UITableViewCell復(fù)用時需要取消cell上各個組件的訂閱
- (void)perpareForReuse {
[super prepareForReuse];
[self.subscription dispose], self.subscription = nil;
}
Delegate的使用
//注意:你必須retain這個delegate對象腿椎,否則他們將會被釋放,你將會得到一個EXC_BAD_ACCESS異常夭咬。添加下列私有屬性到畫廊視圖控制器:
@property (nonatomic, strong) id collectionViewDelegate;
//同時你也需要導(dǎo)入RACDelegateProxy.h,因為他不是ReactiveCocoa的核心部分铆隘,不包含在ReactiveCocoa.h中卓舵。
RACDelegateProxy *viewControllerDelegate = [[RACDelegateProxy alloc]
initWithProtocol:@protocol(FRPFullSizePhotoViewControllerDelegate)];
[[viewControllerDelegate rac_signalForSelector:@selector(userDidScroll:toPhotoAtIndex:) fromProtocol:@protocol(FRPFullSizePhotoViewControllerDelegate)]
subscribeNext:^(RACTuple *value){
@strongify(self);
[self.collectionView
scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:[value.second integerValue] inSection:0]
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:NO];
}];
self.collectionViewDelegate = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UICollectionViewDelegate)];
[[self.collectionViewDelegate rac_signalForSelector:@selector(collectionView:didSelectItemAtIndexPath:)]
subscribeNext:^(RACTuple *arguments) {
@strongify(self);
FRPFullSizePhotoViewController *viewController = [[FRPFullSizePhotoViewController alloc] initWithPhotoModels:self.photosArray currentPhotoIndex:[(NSIndexPath *)arguments.second item]];
viewController.delegate = (id<FRPFullSizePhotoViewControllerDelegate>)viewControllerDelegate;
[self.navigationController pushViewController:viewController animated:YES];
}];
處理異常,完成執(zhí)行刷新操作膀钠,異常打印日志掏湾,執(zhí)行對應(yīng)方法
RAC(self, photosArray) = [[[[FRPPhotoImporter importPhotos]
doCompleted:^{
@strongify(self);
[self.collectionView reloadData];
}] logError] catchTo:[RACSignal empty]];
網(wǎng)絡(luò)請求處理數(shù)據(jù),獲取數(shù)據(jù)返回主線程
+ (RACSignal *)importPhotos {
NSURLRequest *request = [self popularURLRequest];
return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request]
reduceEach:^id(NSURLResponse *response , NSData *data){
//注意:我們可以用下面的reduceEach:替代使用RACTuple的第一個map:肿嘲,以便提供編譯時檢查融击。
return data;
}]
deliverOn:[RACScheduler mainThreadScheduler]]
map:^id (NSData *data) {
id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
return [[[results[@"photo"] rac_sequence]
map:^id (NSDictionary *photoDictionary) {
FRPPhotoModel *model = [FRPPhotoModel new];
[self configurePhotoModel:model withDictionary:photoDictionary];
[self downloadThumbnailForPhotoModel:model];
return model;
}] array];
}] publish] autoconnect];
//信號鏈條最末端的信號操作publish. publish返回一個RACMulitcastConnection,當(dāng)信號連接上時,他將訂閱該接收信號雳窟。autoconnect為我們做的是:當(dāng)它返回的信號被訂閱尊浪,連接到 該(訂閱背后的)信號(underly signal)。
}
信號的信號Signal of signals封救,一個外部信號包含一個內(nèi)部信號拇涤,在輸出信號的subscribeNext:塊中訂閱內(nèi)部信號,會引起嵌套麻煩誉结。使用flattenMap后會生成一個新的信號鹅士,和先前信號平級,訂閱會訂閱到返回的新信號里的值惩坑。map方法也是創(chuàng)建一個新信號掉盅,但是會將返回的信號也當(dāng)做值也拜,這樣就得不到真正需要的值了。
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^RACStream *(id value) {
return [self signInSignal];
}] subscribeNext:^(id x) {
//x
NSLog(@"Sign in result: %@", x);
}];
不同信號順序鏈接趾痘,程序需要等待前一個信號發(fā)出完成事件(sendCompleted)慢哈,然后再訂閱下一個信號(then)
- (RACSignal *)requestAccessToTwitterSignal
{
// 定義一個錯誤,如果用戶拒絕訪問則發(fā)送
NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain code:RWTwitterInstantErrorAccessDenied userInfo:nil];
// 創(chuàng)建并返回信號
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 請求訪問twitter
@strongify(self)
[self.accountStore requestAccessToAccountsWithType:self.twitterAccountType
options:nil
completion:^(BOOL granted, NSError *error) {
// 處理響應(yīng)
if (!granted)
{
[subscriber sendError:accessError];
}
else
{
[subscriber sendNext:nil];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
//throttle可以避免連續(xù)輸入造成的不必要的請求扼脐,then會忽略前一個信號的值岸军,底層的實現(xiàn)是先過濾之前信號發(fā)的值,再使用concat連接then返回的信號瓦侮。
[[[[[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
filter:^BOOL(NSString *text) {
@strongify(self)
return [self isValidSearchText:text];
}]
throttle:0.5]
flattenMap:^RACStream *(NSString *text) {
@strongify(self)
//flattenMap來將每個next事件映射到一個新的被訂閱的信號
return [self signalForSearchWithText:text];
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(NSDictionary *jsonSearchResult) {
NSArray *statuses = jsonSearchResult[@"statuses"];
NSArray *tweets = [statuses linq_select:^id(id tweet) {
return [RWTweet tweetWithStatus:tweet];
}];
[self.resultsViewController displayTweets:tweets];
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
- (RACSignal *)signalForSearchWithText:(NSString *)text {
// 1 - define the errors
NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorNoTwitterAccounts
userInfo:nil];
NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorInvalidResponse
userInfo:nil];
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
@strongify(self);
SLRequest *request = [self requestforTwitterSearchWithText:text];
NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType]; if (twitterAccounts.count == 0) {
[subscriber sendError:noAccountsError];
} else {
[request setAccount:[twitterAccounts lastObject]];
[request performRequestWithHandler: ^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (urlResponse.statusCode == 200) {
NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:nil];
[subscriber sendNext:timelineData];
[subscriber sendCompleted];
} else {
[subscriber sendError:invalidResponseError];
}
}];
}
return nil;
}];
}
異步加載圖片
-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {
RACScheduler *scheduler = [RACScheduler
schedulerWithPriority:RACSchedulerPriorityBackground];
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
UIImage *image = [UIImage imageWithData:data];
[subscriber sendNext:image];
[subscriber sendCompleted];
return nil;
}] subscribeOn:scheduler];
}
cell.twitterAvatarView.image = nil;
[[[self signalForLoadingImage:tweet.profileImageUrl]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];
觀察viewModel里的tableView的數(shù)據(jù)鍵值和全部讀取鍵值艰赞,只要有一個有新值就會調(diào)用
@weakify(self);
[[[RACSignal merge: @[RACObserve(self.viewModel, tweets),
RACObserve(self.viewModel, allTweetsLoaded)]]
bufferWithTime: 0 onScheduler: [RACScheduler mainThreadScheduler]]
subscribeNext: ^(id value) {
@strongify(self);
[self.tableView reloadData];
}];
//bufferWithTime設(shè)置為0是為了避免同一時刻兩個值被同時設(shè)置新值產(chǎn)生了table進(jìn)行了兩次reloadData
封裝hook方法,某個selector被調(diào)用時肚吏,再執(zhí)行一段指定代碼和hook一樣方妖。
@weakify(self);
[[tableView rac_signalForSelector:@selector(layoutSubviews)]subscribeNext:^(id x) {
@strongify(self);
[self doSomethingBeforeTableViewLayoutSubviews];
}];
使用RACCommand來實現(xiàn)按鈕的狀態(tài)根據(jù)輸入郵箱判斷郵箱是否非法還有提交到服務(wù)器后出錯處理等
Demo的github地址:https://github.com/olegam/RACCommandExample
- (void)bindWithViewModel {
RAC(self.viewModel, email) =self.emailTextField.rac_textSignal;
self.subscribeButton.rac_command = self.viewModel.subscribeCommand;
RAC(self.statusLabel, text) =RACObserve(self.viewModel, statusMessage);
}
@interface SubscribeViewModel :NSObject
@property(nonatomic, strong)RACCommand *subscribeCommand; // writeto this property
@property(nonatomic, strong) NSString *email; // read from this property
@property(nonatomic, strong) NSString *statusMessage;
@end
#import "SubscribeViewModel.h"
#import "AFHTTPRequestOperationManager+RACSupport.h"
#import"NSString+EmailAdditions.h"
static NSString *const kSubscribeURL =@"http://reactivetest.apiary.io/subscribers";
@interface SubscribeViewModel ()
@property(nonatomic, strong) RACSignal*emailValidSignal;
@end
@implementation SubscribeViewModel
- (id)init {
self= [super init];
if(self) {
[self mapSubscribeCommandStateToStatusMessage];
}
returnself;
}
-(void)mapSubscribeCommandStateToStatusMessage {
RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
return NSLocalizedString(@"Sending request...", nil);
}];
RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
return[[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
return event.eventType == RACEventTypeCompleted;
}] map:^id(id value) {
return NSLocalizedString(@"Thanks", nil);
}];
}];
RACSignal*failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACSchedulermainThreadScheduler]] map:^id(NSError *error) {
return NSLocalizedString(@"Error :(", nil);
}];
RAC(self,statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];
}
- (RACCommand *)subscribeCommand {
if(!_subscribeCommand) {
@weakify(self);
_subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {
@strongify(self);
return [SubscribeViewModel postEmail:self.email];
}];
}
return _subscribeCommand;
}
+ (RACSignal *)postEmail:(NSString *)email{
AFHTTPRequestOperationManager*manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer= [AFJSONRequestSerializer new];
NSDictionary*body = @{@"email": email ?: @""};
return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];
}
- (RACSignal *)emailValidSignal {
if(!_emailValidSignal) {
_emailValidSignal= [RACObserve(self, email) map:^id(NSString *email) {
return@([email isValidEmail]);
}];
}
return _emailValidSignal;
}
@end
替換Delegate,直接使用RACSubject
RAC內(nèi)存管理
RAC會維護(hù)一個全局的信號集合罚攀,一個或多于一個訂閱者就可用党觅,所有訂閱者都被移除了,信號就被釋放了斋泄。
RAC需要注意的內(nèi)存問題
宏定義
- (void)viewDidLoad
{
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //1
MTModel *model = [[MTModel alloc] init]; // MTModel有一個名為的title的屬性
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
self.flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { //2
return RACObserve(model, title);
}];
[self.flattenMapSignal subscribeNext:^(id x) { //3
NSLog(@"subscribeNext - %@", x);
}];
}
上面的RACObserve會引起引用不釋放的問題杯瞻,通過RACObserve的定義來看看,里面會對self進(jìn)行持有炫掐。
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})
Subject
對subject進(jìn)行map這樣的操作魁莉,這時就需要sendCompleted
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *subject = [RACSubject subject];
[subject.rac_willDeallocSignal subscribeCompleted:^{
NSLog(@"subject dealloc");
}];
[[subject map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
[subject sendNext:@1];
}
但是為什么signal進(jìn)行map操作,不sendCompleted而不會內(nèi)存泄漏呢募胃。因為調(diào)到bind的比如map旗唁、filter、merge痹束、combineLatest检疫、flattenMap等操作如果是RACSubject這樣會持有訂閱者的信號會產(chǎn)生內(nèi)存泄漏需要sendCompleted〉凰唬可以先看看bind的實現(xiàn)
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACStreamBindBlock bindingBlock = block();
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
// 此處省略了80行代碼
// ...
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
didSubscribe的開頭屎媳,就創(chuàng)建了一個數(shù)組signals,并且持有了self论巍,也就是源信號剿牺,也就是訂閱者持有了信號,如果是Subject那么這種信號又會持有訂閱者环壤,這樣就形成了循環(huán)引用晒来。
下面看看sendCompleted如何修復(fù)的內(nèi)存泄漏
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal]; //1
if (signals.count == 0) {
[subscriber sendCompleted]; //2
[compoundDisposable dispose]; //3
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; //4
};
從signals這個數(shù)組中移除傳入的signal,也就是讓訂閱的signal不會持有subject這種信號郑现。
還有replay這樣的操作湃崩,因為這個方法返回的是一個RACReplaySubject
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted]; // 保證源信號發(fā)送完成
return nil;
}];
RACSignal *replaySignal = [signal replay]; // 這里返回的其實是一個RACReplaySubject
[[replaySignal map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"subscribeNext - %@", x);
}];
熱信號冷信號
- 熱信號是主動的荧降,不訂閱也能夠按時發(fā)送。冷信號是被動的攒读,只有訂閱才會發(fā)送朵诫。
- 熱信號可以有多個訂閱者。冷信號只能夠一對一薄扁,有不同訂閱者剪返,消息會從新完整發(fā)送。
RAC的API手冊
常見類
RACSiganl 信號類邓梅。
- RACEmptySignal :空信號脱盲,用來實現(xiàn) RACSignal 的 +empty 方法;
- RACReturnSignal :一元信號日缨,用來實現(xiàn) RACSignal 的 +return: 方法钱反;
- RACDynamicSignal :動態(tài)信號,使用一個 block - 來實現(xiàn)訂閱行為匣距,我們在使用 RACSignal 的 +createSignal: 方法時創(chuàng)建的就是該類的實例面哥;
- RACErrorSignal :錯誤信號,用來實現(xiàn) RACSignal 的 +error: 方法毅待;
- RACChannelTerminal :通道終端尚卫,代表 RACChannel 的一個終端,用來實現(xiàn)雙向綁定尸红。
RACSubscriber 訂閱者
RACDisposable 用于取消訂閱或者清理資源吱涉,當(dāng)信號發(fā)送完成或者發(fā)送錯誤的時候,就會自動觸發(fā)它驶乾。
- RACSerialDisposable :作為 disposable 的容器使用,可以包含一個 disposable 對象循签,并且允許將這個 disposable 對象通過原子操作交換出來;
- RACKVOTrampoline :代表一次 KVO 觀察,并且可以用來停止觀察呐馆;
- RACCompoundDisposable :它可以包含多個 disposable 對象斯议,并且支持手動添加和移除 disposable 對象
- RACScopedDisposable :當(dāng)它被 dealloc 的時候調(diào)用本身的 -dispose 方法。
RACSubject 信號提供者乞旦,自己可以充當(dāng)信號贼穆,又能發(fā)送信號。訂閱后發(fā)送
- RACGroupedSignal :分組信號兰粉,用來實現(xiàn) RACSignal 的分組功能故痊;
- RACBehaviorSubject :重演最后值的信號,當(dāng)被訂閱時玖姑,會向訂閱者發(fā)送它最后接收到的值愕秫;
- RACReplaySubject :重演信號慨菱,保存發(fā)送過的值,當(dāng)被訂閱時戴甩,會向訂閱者重新發(fā)送這些值符喝。可以先發(fā)送后訂閱
RACTuple 元組類,類似NSArray,用來包裝值.
RACSequence RAC中的集合類
RACCommand RAC中用于處理事件的類甜孤,可以把事件如何處理,事件中的數(shù)據(jù)如何傳遞协饲,包裝到這個類中,他可以很方便的監(jiān)控事件的執(zhí)行過程缴川。
RACMulticastConnection 用于當(dāng)一個信號茉稠,被多次訂閱時,為了保證創(chuàng)建信號時二跋,避免多次調(diào)用創(chuàng)建信號中的block战惊,造成副作用,可以使用這個類處理扎即。
RACScheduler RAC中的隊列吞获,用GCD封裝的。
- RACImmediateScheduler :立即執(zhí)行調(diào)度的任務(wù)谚鄙,這是唯一一個支持同步執(zhí)行的調(diào)度器各拷;
- RACQueueScheduler :一個抽象的隊列調(diào)度器,在一個 GCD 串行列隊中異步調(diào)度所有任務(wù)闷营;
- RACTargetQueueScheduler :繼承自 RACQueueScheduler 烤黍,在一個以一個任意的 GCD 隊列為 target 的串行隊列中異步調(diào)度所有任務(wù);
- RACSubscriptionScheduler :一個只用來調(diào)度訂閱的調(diào)度器傻盟。
常見用法
- rac_signalForSelector : 代替代理
- rac_valuesAndChangesForKeyPath: KVO
- rac_signalForControlEvents:監(jiān)聽事件
- rac_addObserverForName 代替通知
- rac_textSignal:監(jiān)聽文本框文字改變
- rac_liftSelector:withSignalsFromArray:Signals:當(dāng)傳入的Signals(信號數(shù)組)速蕊,每一個signal都至少sendNext過一次,就會去觸發(fā)第一個selector參數(shù)的方法娘赴。
常見宏
- RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于給某個對象的某個屬性綁定
- RACObserve(self, name) :監(jiān)聽某個對象的某個屬性,返回的是信號规哲。
- @weakify(Obj)和@strongify(Obj)
- RACTuplePack :把數(shù)據(jù)包裝成RACTuple(元組類)
- RACTupleUnpack:把RACTuple(元組類)解包成對應(yīng)的數(shù)據(jù)
- RACChannelTo 用于雙向綁定的一個終端
常用操作方法
- flattenMap map 用于把源信號內(nèi)容映射成新的內(nèi)容。
- concat 組合 按一定順序拼接信號诽表,當(dāng)多個信號發(fā)出的時候唉锌,有順序的接收信號
- then 用于連接兩個信號,當(dāng)?shù)谝粋€信號完成竿奏,才會連接then返回的信號袄简。
- merge 把多個信號合并為一個信號,任何一個信號有新值的時候就會調(diào)用
- zipWith 把兩個信號壓縮成一個信號泛啸,只有當(dāng)兩個信號同時發(fā)出信號內(nèi)容時绿语,并且把兩個信號的內(nèi)容合并成一個元組,才會觸發(fā)壓縮流的next事件。
- combineLatest:將多個信號合并起來汞舱,并且拿到各個信號的最新的值,必須每個合并的signal至少都有過一次sendNext伍纫,才會觸發(fā)合并的信號。
- reduce聚合:用于信號發(fā)出的內(nèi)容是元組昂芜,把信號發(fā)出元組的值聚合成一個值
- filter:過濾信號莹规,使用它可以獲取滿足條件的信號.
- ignore:忽略完某些值的信號.
- distinctUntilChanged:當(dāng)上一次的值和當(dāng)前的值有明顯的變化就會發(fā)出信號,否則會被忽略掉泌神。
- take:從開始一共取N次的信號
- takeLast:取最后N次的信號,前提條件良漱,訂閱者必須調(diào)用完成,因為只有完成欢际,就知道總共有多少信號.
- takeUntil:(RACSignal *):獲取信號直到某個信號執(zhí)行完成
- skip:(NSUInteger):跳過幾個信號,不接受母市。
- switchToLatest:用于signalOfSignals(信號的信號),有時候信號也會發(fā)出信號损趋,會在signalOfSignals中患久,獲取signalOfSignals發(fā)送的最新信號。
- doNext: 執(zhí)行Next之前浑槽,會先執(zhí)行這個Block
- doCompleted: 執(zhí)行sendCompleted之前蒋失,會先執(zhí)行這個Block
- timeout:超時,可以讓一個信號在一定的時間后桐玻,自動報錯篙挽。
- interval 定時:每隔一段時間發(fā)出信號
- delay 延遲發(fā)送next。
- retry重試 :只要失敗镊靴,就會重新執(zhí)行創(chuàng)建信號中的block,直到成功.
- replay重放:當(dāng)一個信號被多次訂閱,反復(fù)播放內(nèi)容
- throttle節(jié)流:當(dāng)某個信號發(fā)送比較頻繁時铣卡,可以使用節(jié)流,在某一段時間不發(fā)送信號內(nèi)容偏竟,過了一段時間獲取信號的最新內(nèi)容發(fā)出煮落。
UI - Category(常用匯總)
rac_prepareForReuseSignal: 需要復(fù)用時用
- 相關(guān)UI: MKAnnotationView、UICollectionReusableView踊谋、UITableViewCell蝉仇、UITableViewHeaderFooterView
rac_buttonClickedSignal:點擊事件觸發(fā)信號
- 相關(guān)UI:UIActionSheet、UIAlertView
rac_command:button類褪子、刷新類相關(guān)命令替換
- 相關(guān)UI:UIBarButtonItem量淌、UIButton骗村、UIRefreshControl
rac_signalForControlEvents: control event 觸發(fā)
- 相關(guān)UI:UIControl
rac_gestureSignal UIGestureRecognizer 事件處理信號
- 相關(guān)UI:UIGestureRecognizer
rac_imageSelectedSignal 選擇圖片的信號
- 相關(guān)UI:UIImagePickerController
rac_textSignal
- 相關(guān)UI:UITextField嫌褪、UITextView
可實現(xiàn)雙向綁定的相關(guān)API
- rac_channelForControlEvents: key: nilValue:
- 相關(guān)UI:UIControl類
- rac_newDateChannelWithNilValue:
- 相關(guān)UI:UIDatePicker
- rac_newSelectedSegmentIndexChannelWithNilValue:
- 相關(guān)UI:UISegmentedControl
- rac_newValueChannelWithNilValue:
- 相關(guān)UI:UISlider、UIStepper
- rac_newOnChannel
- 相關(guān)UI:UISwitch
- rac_newTextChannel
- 相關(guān)UI:UITextField
Foundation - Category (常用匯總)
NSData
- rac_readContentsOfURL: options: scheduler: 比oc多出線程設(shè)置
NSDictionary
- rac_sequence
- rac_keySequence key 集合
- rac_valueSequence value 集合
NSArray
- rac_sequence 信號集合
NSFileHandle
- rac_readInBackground 后臺線程讀取
NSInvocation
- rac_setArgument: atIndex: 設(shè)置參數(shù)
- rac_argumentAtIndex 取某個參數(shù)
- rac_returnValue 所關(guān)聯(lián)方法的返回值
NSNotificationCenter
- rac_addObserverForName: object:注冊通知
NSObject
- rac_willDeallocSignal 對象銷毀時發(fā)動的信號
- rac_description debug用
- rac_observeKeyPath: options: observer: block:監(jiān)聽某個事件
- rac_liftSelector: withSignals: 全部信號都next在執(zhí)行
- rac_signalForSelector: 代替某個方法
- rac_signalForSelector:(SEL)selector fromProtocol:代替代理
NSString
- rac_keyPathComponents 獲取一個路徑所有的部分
- rac_keyPathByDeletingLastKeyPathComponent 刪除路徑最后一部分
- rac_keyPathByDeletingFirstKeyPathComponent 刪除路徑第一部分
- rac_readContentsOfURL: usedEncoding: scheduler: 比之OC多線程調(diào)用
- rac_sequence
NSURLConnection
- rac_sendAsynchronousRequest 發(fā)起異步請求
NSUserDefaults
- rac_channelTerminalForKey 用于雙向綁定胚股,此乃一
NSEnumerator
- rac_sequence
NSIndexSet
- rac_sequence
NSOrderedSet
- rac_sequence
NSSet
- rac_sequence
RAC圖片版的API手冊
ReactiveCocoa Objective-C
ReactiveCocoa Swift
RXSwift
本文參考整理自
- ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 https://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1
- ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2 https://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2
- iOS的函數(shù)響應(yīng)型編程 https://www.gitbook.com/book/kevinhm/functionalreactiveprogrammingonios/details
- ReactiveCocoa Essentials: Understanding and Using RACCommand http://codeblog.shape.dk/blog/2013/12/05/reactivecocoa-essentials-understanding-and-using-raccommand/
- iOS ReactiveCocoa 最全常用API整理(可做為手冊查詢)http://www.cocoachina.com/ios/20160729/17236.html
- ReactiveCocoa和RXSwift速查表 http://valiantcat.com/2016/07/22/ReactiveCocoa和RXSwift速查表/
- ReactiveCocoa中潛在的內(nèi)存泄漏及解決方案http://tech.meituan.com/potential-memory-leak-in-reactivecocoa.html?from=timeline&isappinstalled=0