ReactiveCocoa Code Practice RAC Network Request Reconfiguration

  • 2021-07-06 11:50:52
  • OfStack

Related reading:

ReactiveCocoa Code Practice-RAC Signal Operation of UI Component

Practice of ReactiveCocoa Code--More Thoughts

Preface

• ES 15EN has the following advantages compared with the previous development mode: (1) it provides the message transmission mechanism of Unified 1; Provides a variety of wonderful and efficient signal operation methods; Cooperate with MVVM design pattern and RAC macro binding to reduce multi-terminal dependency.

The theoretical knowledge of • RAC is very profound, including FRP, high-order functions, cold signal and hot signal, RAC Operation, signal life cycle and so on, which are introduced in these documents. However, due to the characteristics of RAC itself, it may sound easier to get started.

This article starts from a relatively grounding point of view. Because it is basically unrealistic to make a perfect 100% whole project ReactiveCocoa architecture now, and most projects will have a lot of historical burdens, we can only gradually move closer to RAC, and reconstruct the disgusting code to make the logic function clearer.

This section focuses on a simple record of my previous reconstruction of network requests.

1. Normal request refactoring

Old code structure diagram:


In the previous code controllers, one method that needs to connect to the network directly calls the request method of service and obtains the callback, which is a conventional practice.


// controller.m ************************************
//  One of the controllers 1 Method of disposition 
- (void)requestForTop{
[MDSBezelActivityView activityViewForView:self.view withLabel:@" Loading ..."];
//  Direct call service Request method in 
[SXFeedbackService requestForFeedbackSummarySuccess:^(NSDictionary *result) {
[MDSBezelActivityView removeView];
//  Related processing after success 
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[MDSBezelActivityView removeView];
//  Correlation processing after failure 
}];
}

Reconstructed structure diagram:

After rewriting with RAC, controller will not call service directly, and controller can make requests by controlling the execution of command. Once the bound value of 1 changes after getting the data, it will come to the callback method of RACObserve. And if the request fails, it will also be passed as an error signal to the subscribeError callback method of execute. executing can be used to listen for command execution.


// controller.m ************************************
@property(nonatomic,strong)SXFeedbackMainViewModel *viewModel;
- (void)viewDidLoad{
[self addRACObserve];
}
//  Set the binding when the page is first loaded 
- (void)addRACObserve{
@weakify(self);
[[RACObserve(self.viewModel, topNumEntity) skip:] subscribeNext:^(id x) {
@strongify(self);
//  Binding viewModel Value of 1 Once the change came here. 
}];
}
//  The place originally used to send requests 
- (void)requestForTop
{
[[self.viewModel.fetchFeedbackSummaryCommand execute:nil] subscribeError:^(NSError *error) {
//  Handling of errors 
}];
[[self.viewModel.fetchFeedbackSummaryCommand.executing skip:] subscribeNext:^(NSNumber *executing) {
if ([executing boolValue]) {
[MDSBezelActivityView activityViewForView:self.view withLabel:@" Loading ..."];
}else{
[MDSBezelActivityView removeView];
}
}];
}
// viewModel.m ************************************
- (instancetype)init
{
self = [super init];
[self setupRACCommand];
return self;
}
//  Initialization setting 1 Instructions are used to open a request 
- (void) setupRACCommand
{
@weakify(self);
_fetchFeedbackSummaryCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//  A more thorough approach here is to write the request directly as 1 A operation But the network layer of most projects should have manager Or signature and other reasons, it may be more complicated to change it directly to that structure   So the code inside is like RAC And direct request. 
[SXMerchantAutorityService requestForFeedbackSummarySuccess:^(NSDictionary *result) {
@strongify(self);
//  Related actions after successful callback 
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}];
}

2. Requests that need to pass parameters

The above is an ordinary request, that is, the request address is written to death or the parameters are spliced from global variables. If you need to pass in several parameters, controller can't directly contact service, so you need to use viewModel as a medium to pass values. There are two ways to pass values.

1. Pass the properties of viewModel

This method can be used with few parameters, one or two. Add a few attributes directly to viewModel, and then controller assigns a value to this attribute when appropriate. In viewModel in RACCommand call service method need parameters directly from their own attributes.


// controller.m ************************************
self.viewModel.isAccess = self.isAccess;
[self requestForTop];
// viewModel.h ************************************
// input Parameter 
/**
*  Is it a US group or a comment 
*/
@property(nonatomic, assign) BOOL isAccess;
// viewModel.m ************************************
_fetchFeedbackSummaryCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[SXMerchantAutorityService requestForFeedbackSummaryWithType:self.isAccess success:^(NSDictionary *result) {
//  Success 
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//  Failure 
}];
return nil;
}];
}];

If you set some property bindings of viewModel and controller with RAC macro, you can also omit the step of manually assigning values to set method of viewModel. (Dong Boran Blog Park)

2. Parameter transfer through execute method

This method is suitable for properties that cannot be listed as viewModel in 11 cases with many parameters. It is recommended to set up an object model, then build and assign this model before the execute method, and then pass it in as a parameter.

For example, the request method with multiple parameters of this common list class:


// service.h ************************************
/**
*  Get a list of evaluations 
*/
+ (void)requestForFeedbacklistWithSource:(BOOL)isFromWeb
dealid:(NSInteger)dealid
poiid:(NSInteger)poiid
labelName:(NSString *)labelName
type:(NSString *)type
readStatus:(NSString *)readStatus
replyStatus:(NSString *)replyStatus
limit:(NSNumber *)limit
offset:(NSNumber *)offset
success:(void(^)(NSDictionary *result))success
failure:(void(^)(AFHTTPRequestOperation *operation, NSError *error))failure; 

In the controller request method, the old method is to directly call the request interface of service, which is no longer listed here. The writing method of RAC is listed below.


// controller.m ************************************
- (void)requestForDataWithType:(int)type
{
// ------ To RACComand Incoming 1 A input Model. 
SXFeedbackListRequestModel *input = [SXFeedbackListRequestModel new];
input.replyStatus = self.replyStatus; //  It can also be written here 1 Plant method 
input.readStatus = self.readStatus;
input.isMeituan = self.isMeituan;
input.dealid = self.dealid;
input.poiid = self.poiid;
input.type = self.type;
input.labelName = labelName;
input.offset = @(self.offset);
input.limit = @();
//  Above input Is passed here as a parameter 
[[self.viewModel.fetchFeedbackListCommand execute:input] subscribeNext:^(id x) {
// ------ The correct operation is handled here. 
} error:^(NSError *error) {
// ------ Failed operations are handled here. 
}];
}
// viewModel.m ************************************
- (void) setupRACCommand
{
_fetchFeedbackListCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(SXFeedbackListRequestModel *input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//  Use the front execute The incoming parameters will be passed to this place 
[SXMerchantAutorityService requestForFeedbacklistWithSource:input.isFormWeb dealid:input.dealid poiid:input.poiid labelName:input.labelName type:input.type readStatus:input.readStatus replyStatus:input.replyStatus limit:input.limit offset:input.offset success:^(NSDictionary *result) { 
@strongify(self);
// 1 Some operations 
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}];
}

It may seem redundant in this command to pull out every attribute of the previous model and pass it to the parameters. You can rewrite the previous method with many parameters in service to only need to pass in 1 model. Then command can be directly passed into the model here, and it is not troublesome to fetch it out inside the method anyway. On my side, considering the compatibility of other non-RAC places, it has not changed.

3. Eliminate toast when all requests are completed

Here is a concept similar to requesting combo. progressHUD in load is eliminated after all requests are completed. If it can be solved by dispatch scheduling group under ordinary architecture, but RAC is very simple to realize this function, the main method is to judge the status of one command through executing signal, and then use combineLatest operation to monitor the status of multiple command. The characteristic of combineLatest operation is that as long as one of the monitored signals changes, all signals will be returned as one tuple.


//  Eavesdropping executing
RACSignal *hud = [RACSignal combineLatest:@[self.viewModel.fetchFeedbackListCommand.executing,self.viewModel.fetchFeedbackSummaryCommand.executing]];
[hud subscribeNext:^(RACTuple *x) {
if (![x.first boolValue]&&![x.second boolValue]) {
[MDSBezelActivityView removeView];
}else{
[MDSBezelActivityView activityViewForView:self.view withLabel:@" Loading ..."];
}
}];

This recommendation is written in 1 with RACObserve before. It can also be changed to filter.


//  You can put the load HUD Write the code at the front, and then directly control the elimination at the back HUD
[[hud filter:^BOOL(RACTuple *x) {
return ![x.first boolValue]&&![x.second boolValue];
}] subscribeNext:^(id x) {
[MDSBezelActivityView removeView];
}]; 

There is another way to fulfill this requirement, rac_liftSelector. This method calls the @ selector method only when all the signals in the array emit sendNext signals, and the three parameters of this method are emitted by the three sendNext. All of them are back-packaged again, which mainly applies to 3 requests that are asynchronous and have no dependencies.


@weakify(self);
[[self rac_liftSelector:@selector(doWithA:withB:withC) withSignalsFromArray:@[signalA,signalB,signalC]] subscribeError:^(NSError *error) {
@strongify(self);
[MDSBezelActivityView removeView];
} completed:^{
[MDSBezelActivityView removeView];
}]; 

combineLatest and liftselector two combo methods have a definite difference, the specific use can be combined with requirements. The former is that every 1 request comes back will be called back by 1, while the latter is to call the method when all requests come back. (Dong Boran Blog Park)

4. Transfer of result data

If you want all the requests to be completed and all the data to be obtained, then refresh the interface, it is also suitable to use the method of eliminating toast in the above system 1. Change the line that eliminates toast to [self. tableVIew reloadData] or something else.

Because the current mainstream hopes to slim down Controller, it is also recommended to put business logic, judgment, calculation and splicing strings in viewModel, and finally return the required data directly. The controller is only responsible for directly displaying the interface after getting the crisp data. The following example is how to get the text on a text label


// Controller.m ************************************
// ViewDidLoad
RAC(self.replyCountLabel,text) = RACObserve(self.viewModel, replyCountLabelTitle);
// ViewModel.m ************************************
_fetchNewsDetailCommand = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
@strongify(self);
[self requestForNewsDetailSuccess:^(NSDictionary *result) {
//  Leave it here 1 Some empty judgment codes 
self.detailModel = [SXNewsDetailEntity detailWithDict:result[self.newsModel.docid]];
//  And in the middle 1 Some other operations are omitted 
NSInteger count = [self.newsModel.replyCount intValue];
//  Here, the spliced title is directly returned, and more complicated logic will be encountered in reality 
if ([self.newsModel.replyCount intValue] > ) {
self.replyCountBtnTitle = [NSString stringWithFormat:@"%.f Ten thousand posts ",count/.];
}else{
self.replyCountBtnTitle = [NSString stringWithFormat:@"%ld Follow-up post ",count];
}
[subscriber sendCompleted];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[subscriber sendError:error];
}];
return nil;
}];
}]; 

When refactoring, you can put more controller attributes, such as models or arrays, into viewModel. In the former controller, self. replyModels was changed to self. ViewModel. replyModels.


// ViewModel.h ************************************
/**
*  Similar news 
*/
@property(nonatomic,strong)NSArray *similarNews;
/**
*  Search keywords 
*/
@property(nonatomic,strong)NSArray *keywordSearch;
/**
*  Get Search Results Array Command 
*/
@property(nonatomic, strong) RACCommand *fetchNewsDetailCommand;
// ViewModel.m ************************************
//  Something command Inside the callback that successfully calls the requesting method in 
self.similarNews = [SXSimilarNewsEntity objectArrayWithKeyValuesArray:result[self.newsModel.docid][@"relative_sys"]];
self.keywordSearch = result[self.newsModel.docid][@"keyword_search"];
[subscriber sendCompleted];
// Controller.m ************************************
//  Take a random method for example 
- (CGFloat )tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
switch (section) {
case :
return self.webView.height;
break;
case :
return self.viewModel.replyModels.count > ? : CGFLOAT_MIN;
break;
case :
return self.viewModel.similarNews.count > ? : CGFLOAT_MIN;
break;
default:
return CGFLOAT_MIN;
break;
}
}

After a reasonable separation, Controller should have only one UI control, and ViewModel should store model attributes, commands, and one business logic operation or judgment method.

If you are interested in one of the demo codes, you can get the code https://github.com/dsxNiubility/SXNews under fork. I used to write a small project with soil method, but now the old code has moved to old branch, and master branch continues to make some changes related to RAC.

The above is the site to introduce the ReactiveCocoa code practice-RAC network request reconstruction of the relevant content, I hope to help you!


Related articles: