ReactiveCocoa入门教程:第二部分
- (BOOL)isValidSearchText:(NSString *)text { return text.length > 2; }
这就简单的保证了搜索的字符串大于两个字符。写这个很简单的逻辑你可能会问:为什么要分开该方法到工程文件里面呢?
#import <ReactiveCocoa.h>
[[self.searchText.rac_textSignal map:^id(NSString *text) { return [self isValidSearchText:text] ? [UIColor whiteColor] : [UIColor yellowColor]; }] subscribeNext:^(UIColor *color) { self.searchText.backgroundColor = color; }];
想想这是做什么呢?上面的代码:
RACSignal *backgroundColorSignal = [self.searchText.rac_textSignal map:^id(NSString *text) { return [self isValidSearchText:text] ? [UIColor whiteColor] : [UIColor yellowColor]; }]; RACDisposable *subscription = [backgroundColorSignal subscribeNext:^(UIColor *color) { self.searchText.backgroundColor = color; }]; // at some point in the future ... [subscription dispose];
你不会经常做这些,但是你必须知道可能性的存在。
Note:作为这些的一个推论,如果你创建了一个管道,但是你不给他订阅,这个管道将不会执行,这些包括任何侧面的影响,例如doNext:blocks。
Avoiding Retain Cycles
当ReactiveCocoa在场景背后做了好多聪明的事情—这就意味着你不必要担心太多关于信号量的内存管理——这里有一个很重要的内存喜爱那个管的问你你需要考虑。
[[self.searchText.rac_textSignal map:^id(NSString *text) { return [self isValidSearchText:text] ? [UIColor whiteColor] : [UIColor yellowColor]; }] subscribeNext:^(UIColor *color) { self.searchText.backgroundColor = color; }];
__weak RWSearchFormViewController *bself = self; // Capture the weak reference [[self.searchText.rac_textSignal map:^id(NSString *text) { return [self isValidSearchText:text] ? [UIColor whiteColor] : [UIColor yellowColor]; }] subscribeNext:^(UIColor *color) { bself.searchText.backgroundColor = color; }];
#import "RACEXTScope.h"
@weakify(self)然后代码修改后如下:
[[self.searchText.rac_textSignal map:^id(NSString *text) { return [self isValidSearchText:text] ? [UIColor whiteColor] : [UIColor yellowColor]; }] subscribeNext:^(UIColor *color) { @strongify(self) self.searchText.backgroundColor = color; }];
@weakify和@strongify语句是在Extended Objective-C库的宏定义,他们也包含在ReactiveCocoa中。@weakify 宏定义允许你创建一个若饮用的影子变量,@strongify宏定义允许你创建一个前面使用@weakify传递的强引用变量。
Note:如果你对@weakify和@strongify感兴趣,可以进入RACEXTSCope.h中查看其实现。
#import <Accounts/Accounts.h> #import <Social/Social.h>
然后在引入的头文件下面写如下的代码:
typedef NS_ENUM(NSInteger, RWTwitterInstantError) { RWTwitterInstantErrorAccessDenied, RWTwitterInstantErrorNoTwitterAccounts, RWTwitterInstantErrorInvalidResponse }; static NSString * const RWTwitterInstantDomain = @"TwitterInstant";
@property (strong, nonatomic) ACAccountStore *accountStore;
@property (strong, nonatomic) ACAccountType *twitterAccountType;
ACAccountsStore类提供访问你当前设备有的social账号,ACAccountType类代表指定类型的账户。
self.accountStore = [[ACAccountStore alloc] init]; self.twitterAccountType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
这些代码创建了账户存储和Twitter账号标示。在.m中添加如下方法:
- (RACSignal *)requestAccessToTwitterSignal { // 1 - define an error NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain code:RWTwitterInstantErrorAccessDenied userInfo:nil]; // 2 - create the signal @weakify(self) return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { // 3 - request access to twitter @strongify(self) [self.accountStore requestAccessToAccountsWithType:self.twitterAccountType options:nil completion:^(BOOL granted, NSError *error) { // 4 - handle the response if (!granted) { [subscriber sendError:accessError]; } else { [subscriber sendNext:nil]; [subscriber sendCompleted]; } }]; return nil; }]; }
这个方法的作用是:
[[self requestAccessToTwitterSignal] subscribeNext:^(id x) { NSLog(@"Access granted"); } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];
[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];
then方法会一直等待,知道completed事件发出,然后订阅者通过自己的block参数返回,这有效地将控制从一个信号传递给下一个。
Note:上面已经写过了@weakly(self);所以这里就不用再写了。
then方法传递error事件。因此最后的subscribeNext:error: block还接收初始的访问请求错误。
[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];
- (SLRequest *)requestforTwitterSearchWithText:(NSString *)text { NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"]; NSDictionary *params = @{@"q" : text}; SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:params]; return request; }
下一步就是创建一个基于request的信号量。添加如下方法:这创建了一个请求:搜索Twitter(V.1.1REST API)。这个是调用Twitter的api。
- (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]; // 2 - create the signal block @weakify(self) return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { @strongify(self); // 3 - create the request SLRequest *request = [self requestforTwitterSearchWithText:text]; // 4 - supply a twitter account NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType]; if (twitterAccounts.count == 0) { [subscriber sendError:noAccountsError]; } else { [request setAccount:[twitterAccounts lastObject]]; // 5 - perform the request [request performRequestWithHandler: ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { if (urlResponse.statusCode == 200) { // 6 - on success, parse the response NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil]; [subscriber sendNext:timelineData]; [subscriber sendCompleted]; } else { // 7 - send an error on failure [subscriber sendError:invalidResponseError]; } }]; } return nil; }]; }
[[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] flattenMap:^RACStream *(NSString *text) { @strongify(self) return [self signalForSearchWithText:text]; }] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];
运行:
[[[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] flattenMap:^RACStream *(NSString *text) { @strongify(self) return [self signalForSearchWithText:text]; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) { NSLog(@"%@", x); } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];
这样就会在主线程中运行。也就是更新了管道:添加了deliverOn:操作。
#import "RWTweet.h" #import "NSArray+LinqExtensions.h"
[[[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] flattenMap:^RACStream *(NSString *text) { @strongify(self) 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 *)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]; }
这会你一ing该就会很熟悉这种模式了。然后在tableview:cellForRowAtIndex:方法里面添加:
cell.twitterAvatarView.image = nil; [[[self signalForLoadingImage:tweet.profileImageUrl] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(UIImage *image) { cell.twitterAvatarView.image = image; }];
再次运行就可以出来效果了:
[[[[[[[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) 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); }];
你会发现这样就可以了。throttle操作只是发送一个操作,这个操作在时间到之后继续进行。