preface

There are many online documents about the use of Reactive Cocoa, but the application demo is either too small or too large for beginners. After a period of self-study, I have written a complete demo, which fully follows the ARCHITECTURE design of MVVM. Making portal

This article is not suitable for children’s shoes without any Reactive Cocoa foundation. Please refer to the basics of Reactive Cocoa. IOS ReactiveCocoa most commonly used API (can be used as a manual query) ReactiveCocoa official GitHub An architectural overview of ReactiveCocoa V2.5 source code parsing

Run the demo

  • Please proceed before runningpod install
  • After running, the search button is unavailable if the search box is not entered. Enter the search boxThe movie nameorThe director ofAnd so on, such as Zhang Yimou, the search button can be used, click the button can get the relevant movie search results;
  • The demo network data adopts the movie search function in Douban Api V2;

Programming instructions

The main interface

  • The main interface consists of two classesHomeViewControllerandHomeViewModelBecause model is too simple, it is defined directly in ViewModel
  • HomeViewModelDefines the search criteriasearchConditonsString and signal whether the string is empty with the button availablesearchBtnEnableSignalBinding;
  • HomeViewControllerFirst place the ViewModelsearchConditonsThe string is bound to the content of the input box, and then the search button’senableProperty and ViewModelsearchBtnEnableSignalBinding;
  • The above two steps can be realized by judging whether the content of the input box is empty to determine the search buttonenableProperties;
  • Click the button to jump to the page;

HomeViewModel is defined as follows:

#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>

@interface HomeViewModel : NSObject
@property (nonatomic, copy) NSString *searchConditons;

@property (nonatomic, strong, readonly) RACSignal  *searchBtnEnableSignal;
@end

Copy the code
#import "HomeViewModel.h"

@implementation HomeViewModel

-(instancetype)init{
    if (self = [super init]) {
        [self setUp];
    }
    return self;
}

- (void)setUp{
    [self setupSearchBtnEnableSignal];
}

- (void)setupSearchBtnEnableSignal {
    _searchBtnEnableSignal = [RACSignal combineLatest:@[RACObserve(self, searchConditons)] reduce:^id(NSString *searchConditions){
        return @(searchConditions.length);
    }];
}

@end
Copy the code

HomeViewController is defined as follows:

#import "HomeViewController.h"
#import "MovieViewController.h"
#import "UIButton+FillColor.h"
#import "HomeViewModel.h"
#import <UIButton+JKBackgroundColor.h>

@interface HomeViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textContent;
@property (weak, nonatomic) IBOutlet UIButton *btnSearch;

@property(nonatomic, strong) HomeViewModel *homeVM;

@end

@implementation HomeViewController

-(HomeViewModel *)homeVM{
    if(! _homeVM) { _homeVM = [[HomeViewModel alloc]init]; }return  _homeVM;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _btnSearch.enabled = false;
    
    [_btnSearch setBackgroundColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
    [_btnSearch setBackgroundColor:[UIColor blueColor] forState:UIControlStateNormal]; RAC(self.homeVM, searchConditons) = self.textContent.rac_textSignal; RAC(self.btnSearch, enabled) = self.homeVM.searchBtnEnableSignal; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.} - (IBAction)onClick (UIButton *)sender {// enter the next interface UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    MovieViewController * destViewController = [storyboard instantiateViewControllerWithIdentifier:@"MovieViewController"];
    destViewController.conditions = _textContent.text;
    [self.navigationController pushViewController:destViewController animated:YES];
    
}


@end

Copy the code

Search results interface

  • The search results interface consists of three classesMovieandMovieViewModelAs well asMovieViewController;
  • MovieIncluding film title, date, director, leading actors and pictures; In fact,Douban Api V2There’s a lot more movie data to be returned, so here’s a selection;
#import <Foundation/Foundation.h>

@interface Movie : NSObject

@property(nonatomic, strong) NSString * title;
@property(nonatomic, strong) NSString * year;
@property(nonatomic, strong) NSArray *casts;
@property(nonatomic, strong) NSArray *directors;
@property(nonatomic, strong) NSDictionary *images;

+ (instancetype)movieWithDict:(NSDictionary *)dict;

@end
Copy the code
#import "Movie.h"

@implementation Movie

+(instancetype)movieWithDict:(NSDictionary *)dict{
    Movie *movie = [[Movie alloc]init];
    movie.year = dict[@"year"];
    movie.title = dict[@"title"];
    movie.casts = dict[@"casts"];
    movie.directors = dict[@"directors"];
    movie.images = dict[@"images"];
    
    return movie;
}

@end
Copy the code
  • MovieViewModelIt includes business logic code: define commands, network requests, get data, send data,

Note: RACSignal is used here, not RACSignal. Beginners may struggle to understand the difference between the two. RACCommand works both ways: the speaker gives a speech, the audience below hears it and gives feedback, and the speaker responds to that feedback. In this demo, the MovieViewController sends a command, the MovieViewModel receives the command, makes a network request, and sends the network data packet, and the MovieViewController parses and displays the received data.

The definition is as follows:

#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>

@interface MovieViewModel : NSObject

@property (nonatomic, strong, readonly) RACCommand *requestCommand;
@property (nonatomic, copy, readonly) NSArray *movies;

@end
Copy the code
#import "MovieViewModel.h"
#import "NetworkManager.h"
#import "Movie.h"


@implementation MovieViewModel

-(instancetype)init{
    if (self = [super init]) {
        [self setup];
    }
    return self;
}

- (void)setup {
    
    _requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        NSLog(@"% @", input);
        
        
        RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            NetworkManager *manager = [NetworkManager manager];
            [manager getDataWithUrl:@"https://api.douban.com/v2/movie/search" parameters:input success:^(id json) {
                [subscriber sendNext:json];
                [subscriber sendCompleted];
            } failure:^(NSError *error) {
                
            }];
            
            return nil;
        }];
        return [requestSignal map:^id _Nullable(id  _Nullable value) {
            NSMutableArray *dictArray = value[@"subjects"];
            NSArray *modelArray = [dictArray.rac_sequence map:^id(id value) {
                return [Movie movieWithDict:value];
            }].array;
           NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"year" ascending:NO];
            _movies = [modelArray sortedArrayUsingDescriptors:@[sortDescriptor]];
            NSLog(@"% @",_movies.description);
            
            return nil;
        }];
    }];
    
}

@end
Copy the code
  • MovieViewControllerIt includes sending commands, data parsing, and data display. The definition is as follows:
#import <UIKit/UIKit.h>

@interface MovieViewController : UITableViewController

@property(nonatomic, copy)NSString *conditions;

@end
Copy the code
#import "MovieViewController.h"
#import "Movie.h"
#import "MovieViewModel.h"
#import "MovieCell.h"
#import <YYWebImage/YYWebImage.h>
#import <ProgressHUD.h>
#import <SVProgressHUD.h>
#import "UITableView+FDTemplateLayoutCell.h"

@interface MovieViewController ()
@property (nonatomic, strong)MovieViewModel *movieVM;
@end

@implementation MovieViewController

-(MovieViewModel *)movieVM{
    if(! _movieVM) { _movieVM = [[MovieViewModel alloc]init]; }return_movieVM; } - (void)viewDidLoad { [super viewDidLoad]; [self.movieVM.requestCommand.executionSignals.switchToLatest subscribeNext:^(id x) { [self.tableView reloadData];  [SVProgressHUD dismiss]; }]; NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; parameters[@"q"] = _conditions;
    [self.movieVM.requestCommand execute:parameters];
    [SVProgressHUD show];
    
    self.tableView.fd_debugLogEnabled = YES;  
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.movieVM.movies.count;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return [tableView fd_heightForCellWithIdentifier:@"cellID" configuration:^(MovieCell* cell) {
        [self configureCell:cell atIndexPath:indexPath];
    }];
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MovieCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath];
    
    [self configureCell:cell atIndexPath:indexPath];
    
    
    return cell;
}

- (void)configureCell:(MovieCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    
    Movie *movie = self.movieVM.movies[indexPath.row];
    
    NSDictionary *dicImage = movie.images;
    NSString *imageStr = dicImage[@"large"];
    NSURL *imageUrl = [NSURL URLWithString:imageStr];
    
    // progressive
    [cell.movieImageView yy_setImageWithURL:imageUrl options:YYWebImageOptionProgressive];
    
    // progressive with blur and fade animation (see the demo at the top of this page)
    [cell.movieImageView yy_setImageWithURL:imageUrl options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation];
    
    cell.title.text = movie.title;
    NSString *year = @"Release time :";
    cell.year.text = [year stringByAppendingString:movie.year];
    NSString *directors = @"Director:";
    for (NSDictionary *dict in movie.directors) {
        NSString *directname = dict[@"name"];
        directors = [directors stringByAppendingFormat:@"% @.",directname];
    }
    cell.directors.text = directors;
    NSString *casts = @"Starring:";
    for (NSDictionary *dict in movie.casts) {
        NSString *castname = dict[@"name"];
        casts = [casts stringByAppendingFormat:@"% @.",castname];
    }
    cell.casts.text = casts;
        
}

Copy the code

Reference: IOS ReactiveCocoa most commonly used API (can be used as a manual query) ReactiveCocoa official GitHub An architectural overview of ReactiveCocoa V2.5 source code parsing