打算在项目中大面积使用RAC来开发,所以整理一些常用的实践范例和比较完整的api说明方便开发时随时查阅
函数反应式编程是声明式编程的子编程范式之一
需要满足两个条件
objectivec里使用block作为函数
[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop)
{
NSLog(@"%@",number);
}];
NSArray * mappedArray = [array rx_mapWithBlock:^id(id each){
return @(pow([each integerValue],2));
}];
NSArray *filteredArray = [array rx_filterWithBlock:^BOOL(id each){
return ([each integerValue] % 2 == 0);
}]
[[array rx_mapWithBlock:^id (id each){
return [each stringValue];
}] rx_foldInitialValue:@"" block:^id (id memo , id each){
return [memo stringByAppendingString:each];
}];
用函数生成另一个函数
func filterGenerator(lastnameCondition: String) -> (Staff) -> (Bool) {
return {staff in
return staff.lastname == lastnameCondition
}
}
let filterWang = filterGenerator("Wang")
let filterHu = filterGenerator("Hu")
staffs.filter(filterHu)
NSArray *array = @[ @1, @2, @3 ];
RACSequence * stream = [array rac_sequence];
//RACSequence是一个RACStream的子类。
[stream map:^id (id value){
return @(pow([value integerValue], 2));
}];
//RACSequence有一个方法返回数组: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
+ (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;
}
+ (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];
}
- (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];
}
- (void)prepareForReuse {
[super prepareForReuse];
[self.subscription dispose], self.subscription = nil;
}
//注意:你必须retain这个delegate对象,否则他们将会被释放,你将会得到一个EXC_BAD_ACCESS异常。添加下列私有属性到画廊视图控制器:
@property (nonatomic, strong) id collectionViewDelegate;
//同时你也需要导入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];
}];
RAC(self, photosArray) = [[[[FRPPhotoImporter importPhotos]
doCompleted:^{
@strongify(self);
[self.collectionView reloadData];
}] logError] catchTo:[RACSignal empty]];
+ (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,当信号连接上时,他将订阅该接收信号。autoconnect为我们做的是:当它返回的信号被订阅,连接到 该(订阅背后的)信号(underly signal)。
}
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^RACStream *(id value) {
return [self signInSignal];
}] subscribeNext:^(id x) {
//x
NSLog(@"Sign in result: %@", x);
}];
- (RACSignal *)requestAccessToTwitterSignal
{
// 定义一个错误,如果用户拒绝访问则发送
NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain code:RWTwitterInstantErrorAccessDenied userInfo:nil];
// 创建并返回信号
@weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 请求访问twitter
@strongify(self)
[self.accountStore requestAccessToAccountsWithType:self.twitterAccountType
options:nil
completion:^(BOOL granted, NSError *error) {
// 处理响应
if (!granted)
{
[subscriber sendError:accessError];
}
else
{
[subscriber sendNext:nil];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
//throttle可以避免连续输入造成的不必要的请求,then会忽略前一个信号的值,底层的实现是先过滤之前信号发的值,再使用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;
}];
@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设置为0是为了避免同一时刻两个值被同时设置新值产生了table进行了两次reloadData
@weakify(self);
[[tableView rac_signalForSelector:@selector(layoutSubviews)]subscribeNext:^(id x) {
@strongify(self);
[self doSomethingBeforeTableViewLayoutSubviews];
}];
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
RAC会维护一个全局的信号集合,一个或多于一个订阅者就可用,所有订阅者都被移除了,信号就被释放了。
- (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进行持有。
#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进行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进行map操作,不sendCompleted而不会内存泄漏呢。因为调到bind的比如map、filter、merge、combineLatest、flattenMap等操作如果是RACSubject这样会持有订阅者的信号会产生内存泄漏需要sendCompleted。可以先看看bind的实现
- (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的开头,就创建了一个数组signals,并且持有了self,也就是源信号,也就是订阅者持有了信号,如果是Subject那么这种信号又会持有订阅者,这样就形成了循环引用。
下面看看sendCompleted如何修复的内存泄漏
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这个数组中移除传入的signal,也就是让订阅的signal不会持有subject这种信号。
还有replay这样的操作,因为这个方法返回的是一个RACReplaySubject
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted]; // 保证源信号发送完成
return nil;
}];
RACSignal *replaySignal = [signal replay]; // 这里返回的其实是一个RACReplaySubject
[[replaySignal map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"subscribeNext - %@", x);
}];