In fact, this is probably the most common usage scenario. There is a class of business that can be abstracted into a sequence of sequential tasks, such as the following code
/* ColdSignal Completion: decode success; Error: FMBarCodeServiceErrorDomain || NSURLErrorDomain || RACSignalErrorDomain with RACSignalErrorTimedOut //4 Scheduler: specified; Multicast: NO; */ - (RACSignal *)decodeBarWithURLString: (NSString *)urlString { NSParameterAssert(urlString ! = nil); @weakify(self); return [[[self getUIImageWithURLString:urlString] //1 flattenMap:^(UIImage *image) { @strongify(self); return [self decodeBarWithUIImage:image]; / / 2}] a timeout: 1.5 onScheduler: [RACScheduler schedulerWithPriority: RACSchedulerPriorityDefault]]. / / 3}Copy the code
This code does not have to be complicated, just pass in the URL of an image, download the corresponding image, and try to decode the QR code of the image:
- The little task that’s done in getUIImageWithURLString, is to download UIImage. When the download fails, an NSError for the NSURLErrorDomain is raised.
- The small task here is to decode the TWO-DIMENSIONAL code of UIImage obtained in the previous step. When decoding failure, will send a FMBarCodeServiceErrorDomain NSError (own business code defined in the error domain).
- The business requirements, here is long when the user according to the picture, an option pop-up menu, allows users to choose the appropriate operation, such as “save image”, “forward photos” and so on, at the same time, if can identify the qr code in the image, option in the pop-up menu, also contains a “qr code to identify the picture”. In some cases, even if the image itself is indeed a TWO-DIMENSIONAL code, the two-dimensional code may be very complex, and the parsing time will be relatively long. In order to ensure the best user experience, a timeout logic is needed here. If 1.5 seconds have not resolved a valid TWO-DIMENSIONAL code, abandon the current parsing action. The timeout operation is used for this scenario. When the timeout period is reached and the Next event has not been sent, an NSError of the RACSignalErrorDomain is sent in the Pipeline. The error code is RACSignalErrorTimedOut.
- The Pipeline is composed of several small tasks, each of which may send an error, so for the subscriber of the Pipeline, the captured NSError will be one of several different domains.
The subscriber code for the Pipeline would look something like this:
-(void)jsCallImageClick:(NSString *)imageUrl imageClickName:(NSString *)imgClickName { NSMutableArray *components = [NSMutableArray arrayWithArray:[imageUrl componentsSeparatedByString:@"&qmSrc:"]]; NSMutableArray *temp = [NSMutableArray arrayWithArray:[(NSString*)[components firstObject] componentsSeparatedByString:[NSString stringWithFormat:@"&%@:",imgClickName]]]; [self filterJsArray:temp]; NSString *imageUrlString = [NSString stringWithFormat:@"%@",(NSString *)[temp firstObject]]; RACSignal *barCodeStringSignal = [self.barCodeService decodeBarWithURLString:imageUrlString]; @weakify(self); [[barCodeStringSignal deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSString *barCodeString) { @strongify(self); [self showImageSaveSheetWithImageUrl:imageUrl withImageClickName:imgClickName withBarCode:barCodeString]; } error:^(NSError *error) { @strongify(self); [self showImageSaveSheetWithImageUrl:imageUrl withImageClickName:imgClickName withBarCode:nil]; } completed:^{ }]; }Copy the code
Because decodeBarWithURLString uses timeout inside the RACScheduler to switch to a background thread, So switch back to the RACScheduler mainThreadScheduler at subscriber (UI).
Catch and replace errors
The following is also a code snippet from a real business scenario, with appropriate omissions. The requirements can be roughly described as follows: FMContact. ContactItems array contains all is a contact email address (at least one), when using FMContactCreateAvatarCell shows the head of the contact, through one email address, Construct a URL, download the avatar, and then set the avatar image to UIButton.
ColdSignal Completion: download image finished; Error: Scheduler: specified; Multicast: NO; */ - (RACSignal *)getAvatarWithContact: (FMContact *)contact { RACSignal *addrs = [[contact.contactItems.rac_sequence map:^(FMContactItem *contactItem) { return contactItem.email; }] signal]; return [[[[addrs take:1] map:^id(NSString *emailAddr) { return [[[FMAvatarManager shareInstance] rac_asyncGetAvatar:emailAddr] retry:3]; }] flatten] catch:^RACSignal *(NSError *error) { return [RACSignal return:nil]; }]; } - (void)initPipelineWithCell:(FMContactCreateAvatarCell *)cell { @weakify(cell); [[[[self getAvatarWithContact:self.contact] deliverOnMainThread] takeUntil:cell.rac_prepareForReuseSignal] subscribeNext:^(UIImage *image) { @strongify(cell); if (image) { [cell.avatarButton setImage:image forState:UIControlStateNormal]; } } error:^(NSError *error) { } completed:^{ }]; }Copy the code
This business requirement does not seem too difficult. We can write it in traditional code, but if we use FRP, we can write the logic more clearly with declarative code:
- GetAvatarWithContact defines a Signal. By entering the FMContact parameter, obtain a corresponding avatar. If the avatar is downloaded successfully, the image will be sent to the Pipeline subscriber via next. It doesn’t send an error, it sends a nil inside next.
- The Pipeline will only have one next event, which by Signal’s definition may be nil, so you need to check.
- The Pipeline does not generate errors, so there is no need to do anything. [[FMAvatarManager shareInstance] rac_asyncGetAvatar:emailAddr] is a signal with an error event. The interesting part is how to handle errors that might occur here, so read on.
- Send the email address in the FMContact.contactItems array as signal.
- FMContact has at least one email address. Since only one avatar needs to be displayed, the easiest way is to fetch the first email address through take operation.
- From a module design point of view, there is a basic rule to follow: if a small task is likely to fail, send an error message through error. [[FMAvatarManager shareInstance] rac_asyncGetAvatar:emailAddr] [FMAvatarManager shareInstance] rac_asyncGetAvatar:emailAddr] [FMAvatarManager shareInstance] rac_asyncGetAvatar:emailAddr] However, for better user experience, a link can be added to the Pipeline and a strategy can be added, which is to automatically download again when the download fails. A total of three attempts can be made. This requirement can be easily realized with retry operation.
- If three downloads fail, the Pipeline will still send an error, but the getAvatarWithContact signal is designed to avoid error, in which case the catch operation should be used. What a catch does is when an error occurs in a Pipeline, it “eats” it and replaces it with another signal so that the Pipeline can continue sending next data.
- [RACSignal return:nil] is the replacement signal that sends nil inside next once and completes immediately. (if the business requirements change, here can also be sent via [RACSignal return: defaultAvatarImage] a default avatar images, Pipeline is very convenient, can be flexible assembly).