One, foreword

Long before you want to write about their experience in the design pattern, but always feel themselves, the frog in well, after all, the more in-depth in the field of iOS feel my ignorance, has the fear of the heart, more have no confidence to write this thing (you can also understand is don’t have the time (> man <), please forgive my pack to force, hey hey).

It’s easy to say that design patterns are a love-hate thing, but there are a thousand Hamlets in a thousand people’s eyes, and it’s true that there are a thousand hamlets in a thousand people’s eyes, and it’s really addictive when you get into it!

A few days ago, I wrote a comprehensive Analysis of iOS Xcode, which was read more than a thousand times in just one day, and even appeared on the front page of the book. I was really happy. Yesterday, I explained my understanding and application of MVVM with a friend of mine by code. Head a hot to make a piece to share with you is understandable, of course, more hope that there are more gods to give advice, let myself let you have to improve enough, thank you very much!

Talk about MVVM and RAC

1. Analysis of MVVM

To here I default you have read MVVM related articles (after all related articles can be used to describe ~(≧▽≦)/~ la la la!) , just briefly my understanding of it.

MVC is the standard mode for building iOS App, and an authoritative paradigm recommended by Apple for organizing code. Most apps on the market are built in this way. The specific mode of building is not detailed, and iOS beginners are familiar with it (although they may not be able to follow it completely). However, its inevitable problems are serious problems for developers, such as heavy ViewController, missing network logic (there is no place for it), poor testability, etc. Therefore, a new architecture with high maintainability and low coupling (MVVM) is popular.

MVVM, though from Microsoft, should not be deprecated. It formalizes the tightly coupled nature of views and controllers, as shown below:

ViewModel: A comparison to the ViewModel introduced by MVC. Is view shows logic, validation logic, such as network request code storage place, the only note that any reference view itself should not be placed in the VM, in other words, don’t introduce VM UIKit. H (for image this, others will be as to process the data, it depends on the individual idea, does not affect the overall architecture).

This first solves the VC bloat problem by writing logical code, network requests, etc. into the VM, and since the VM contains all the presentation logic and does not reference V, it is programmatically testable.

So that’s what it looks like. 6666!

2. RAC shallow analysis

Very shallow… This article will focus on the frame and actual combat and the idea of MVVM, RAC this words learning curve for longer, it is difficult to understand, not to fit in because when users, before learning Chinese tutorial also is less, so more effort, learning to use up (was taken with great feel well, power pack to force a @ % & $% &) but is now mature rotten street, As long as you have the heart, a lot of good tutorials, can dive down to see me write hydrology, take down RAC is not a problem!

ReactiveCocoa can be said to be a framework that combines functional programming and responsive programming, or it can also be called functional Responsive programming (FRP) framework. It is emphasized that while RAC provides a single, unified approach to handle asynchronous behavior, These include delegate methods,blocks callbacks,target-action mechanisms,notifications, and KVO. But don’t dismiss it as simply reducing code complexity to better match MVVM, boy.

The biggest difference is that it provides a new way of thinking about writing code. Since RAC adds SUPPORT for KVO, UIKit events, delegates, and selectors in Cocoa, there is no need to do much cross-function work.

If all the engineering use of RAC, for the same business logic can finally completed in the same block of code, the UI events, logical processing, a file or database operations, asynchronous network request, UI, according to the results of a large set of all nested in functional programming thread, enter the page set up good all this relationship, After the user clicks, there is no problem waiting for this set of links to be triggered one by one according to the desired logic and sequence, and finally shown to the user.

(≧▽≦!)/~ la la la la!

3. Understanding and application of both

In this tutorial, we will use the MVVM+RAC combination to create a list of pull-up loads and pull-down sprints, so it is more about MVVM than RAC logic chain operations. The larger role RAC plays is better decoupled. Reduce code complexity and make the code hierarchical and logical, facilitating maintenance and upgrade.

Second, the framework

1. Detailed explanation of the framework directory

First of all, let’s introduce the directory structure of this framework, as shown below

1, Frameworks,

This is the “Frameworks” folder that you need to add to the Build Phases. The libraries you add to the Build Phases will be automatically placed in the “Frameworks” folder and will not be displayed outside the Phases, disrupting the directory structure. The process of adding the system library is as follows:

In addition, a careful observer will notice that there are two identical Frameworks in this directory. What are these? At that time, the project was not so naughty. The problem is that the following Pods Target automatically generates a virtual Frameworks folder, so why not use the following one? What a load of crap! There’s no conflict anyway, just keep it on.

Now that we’re talking about Pods, let’s talk about CocoaPods.

2, CocoaPods

When you’re developing an iOS app, there are a lot of third party open source libraries like JSONKit, AFNetWorking, etc. One library may use other libraries, so to use it, you have to download other libraries, and other libraries use other libraries, “children and grandchildren are endless.” Anyway, I have experienced this pain in the early days, so it is very troublesome to manually download the required libraries one by one.

Another common situation is when the library you’re using in your project gets updated and you have to re-download the new version and rejoin the project, which can be very troublesome.

As iOS programmers, it is essential to master the basic skills of CocoaPods, so how to use this tool?

Haha ~ do you think I’ll tell you? Ok, I’m still a soft touch. Here’s how to use it… (one item from the danjun 乛 乛 roster)

☝(•̀˓◞•́) Oh, is it possible that you have developed a new skill? 6666!

3, AppDelegate

This directory holds the appdelegate.h (.m) file, which is the entry file for the entire application, so separate it out. I’ll show you how to write a simple AppDelegate in a moment, which will add some classes to this folder, so it’s worth putting them in a folder.

4, Class

The project body class, where most of the daily development code is located, is subdivided into several subdirectories.

Generic class

  • General :Generic class (folder project migration process does not need to change can be used directly)
    • -Leonard: The Base class of the entire framework.
    • Categories: Common extension classes (common Categories such as sharing)
    • Core: common Core classes (usually storing personal information, interfaces, apis, etc.)
    • Models: Common data Models
    • Views: Public View (some common Views encapsulated)

Utility class

  • Helpers: tools such as data request, form upload, network monitoring, etc.

Macro definition class

  • Macro :Macro definition classes (i.e. macro definitions used throughout the application)
    • The relevant macro definitions for the appMacro.h app project
    • Notificationmacro.h NotificationMacro.h notificationMacro.h notificationMacro.h notificationMacro.h notificationMacro.h
    • Vendormacro. h Specifies third-party macros
    • Utilsmacro.h is a macro definition for simplifying code
    • . And so on and so on (the rest is up to you! Y ^ o ^) Y

APP concrete module code class

  • Sections :Folders for each module (generally, we use people)
    • LSSections Folder of Wang Longshuai
    • CLSections Specifies the folder for Marin
    • . (You can also write your favorite old teacher, diao Diao!)

Under the folder of each member is the folder of the module he/she is responsible for. For example, Teacher Cang is responsible for PHP interface module (I also think PHP is the best language! You can talk about it in the comments section! One item from the danjun 乛 : one item 乛), as follows (following the personal folder above) :

  • PHP :The module name can also be a HomePage… , etc.
    • ViewControllers where the controller is stored (this is the folder name)
    • ViewModels (MVVM core, decoupling, processing logic, etc.)
    • Views interface related View store is not (interface related sub-view)
    • Models Where data Models are stored (pure data Models, not fat at all, standard thin Models)

This is the standard MVVM… Why don’t you link it to the list above? Why? Why? Because I can’t do it!! (Can not level 3, level 4 list MarkDown writing method, beg god to move! Every good day brings thanks!

Third Party Libraries

  • Vendors: Third party libraries/SDKS, UMeng, WeiboSDK, WeixinSDK, etc.

(๑⁼̴̀д⁼̴́๑) Mode ヤ Before this year What are you doing? There’s CocoaPods, a third party library management program that you just created: ԅ(¯་། ¯ԅ).

CocoaPods does manage most of the third party libraries. There are two reasons to create a third party library directory: First, not all the third parties you need will support Pods, so you’ll need to manually add some libraries. Second, some third-party libraries support Pods, but we need to change or even customize the third party. In this case, we also need to put it here, in case you accidentally update your customization with Pods! ᕕ(ᐛ)ᕗ You hit me!

5, the Resource

Here are some of the resources required for the project, as follows

  • Fonts font
  • Images (of course you can add them to assets. xcassets, no one can stop you)
  • A contributor voice
  • Videos video

Ok, so much for the catalogue! Want to know more detailed can private letter me!

2, base class details

Here we will focus on the base classes of VC, V and VM. Other modes are similar to View, so we will skip them. The base class of TableViewCell is slightly special, so we will also mention it.

My current base class looks like the following:

Isn’t it dazzling… I used to hate it, tried to kill all the base classes, and had some trouble… At the end of the article I can talk to you about how I tried and failed to kill base classes, so let’s talk about base classes in more detail.

1, YDViewController


The specific purpose of the function has been marked very clear, here simply talk about the role of four functions

  • Yd_addSubviews: Adds a View to a ViewController

  • Yd_bindViewModel: used to bindV (VC) to VM

  • Yd_layoutNavigation: Set navigation bar and column

  • Yd_getNewData: called when data is first fetched (not necessary)

2, YDView


  • Yd_setupViews: Add child View to main View
  • Yd_bindViewModel: Bind V to VM
  • Yd_addReturnKeyBoard: Set click blank keyboard recycle

3, YDViewModel


  • Yc_initialize: Performs some logical bindings and handles network data requests.
  • LSRefreshDataStatusIdentifies the operation to be performed after data processing
    • The LSHeaderRefresh_HasMoreData drop-down has more data
    • The LSHeaderRefresh_HasNoMoreData drop down has no more data
    • LSFooterRefresh_HasMoreData there is more data to pull
    • LSFooterRefresh_HasNoMoreData No more data was pulled up
    • LSRefreshError Indicates a refresh error
    • LSRefreshUI just refreshes the UI layout

4, YDTableViewCell

Because the Cell is special, we will mention it separately. If you look at the base classes of ViewMdoel, View, etc., you will find that each base class has a place for data binding, but the data binding of cell needs to be placed at the time of data initialization, because all the base class data logical binding is called when the initialization object is not returned. However, there will be problems if data binding is performed in the cell, as shown in the following figure:

The above function will fail to reuse if it is in bindViewModel and will not respond when the button is clicked, but if it is called when the data is initialized: For example, setViewModel will be OK because the rac_prepareForReuseSignal reuse mechanism that uses the cell in RAC is invalid before the cell is initialized and returned.

3. Digress

The function of base class is unified management, unified style, easy to code, if there are more additional functions, it is recommended to use Protocol or Category, so that the portability is strong, easy to manage and expand, and will not affect the whole.

Core in this base class is to configure and use the VM V (VC), and provide some necessary Protocol methods to deal with the interface display, logic, the code style standardization, each part of the function is clear, in this way, when you need to write what, need to find what you need to change something will clear the location of the code, logic clearer, Instead of wasting more time thinking about where to write, where to look, where to change things that shouldn’t be time consuming.

Iii. Actual combat part (realization of classic list)

Here is a common list of the code construction methods of the following interfaces :(I am too lazy to write any more. This is an interface of a project I did before. It can be seen in the explanation of the base classes before that they all start with YD, but here they start with YC.

First look at the interface. The requirement is that the content in the header can be swiped left and right if it is large, and then the whole thing can be pulled up and loaded. Here’s how I deal with it: First the entire interface is a TableView, then divided into a Header, a Section, and a body list Row. Nesting a CollectionView on the Header ensures reuse. The specific layers are as follows

Then the directory after processing is as follows:

A brief introduction:

  • ViewController
    • LSCircleListViewController: main controller interface, responsible for the jump, Navgation, TabBar, etc
  • View
    • LSCircleListView: main interface View, responsible for the main interface display
    • LSCircleListHeaderView: Header, encapsulated inside a CollectionView
    • LSCircleListCollectionCell: head CollectionView custom Cell in the Header
    • LSCircleListSectionHeaderView: SectionView, this interface does not need to reuse, so simple a View, if need to reuse TableViewHeaderFooterView
    • LSCircleListTableCell: Cell of the main TableView
  • ViewModel
    • LSCircleListViewModel: the main ViewModel of the interface
    • The Header corresponding ViewModel LSCircleListHeaderViewModel: head
    • CollectionCell LSCircleListCollectionCellViewModel: head and TableViewCell ViewModel (because of the data structure is the same)
    • The ViewModel LSCircleListSectionHeaderViewModel: Section
  • Model
    • LSCircleListModel: Data model for the circle (header and tableViewCell data structures are the same)

So many classes for a small interface… Is this too much to take? Calm down, Slut! You have to think about what’s the point of putting all these things in VC? It would take thousands of rows! (Exaggerating! But also enough headache), so many classes, here focus on the main VC, main V, main VM, main M ok, can explain in detail between MVVM is how to work on everything.

1, LSCircleListViewController processing

Code first:

//
// LSCircleListViewController.m
// ZhongShui
//
// Created by Wang Longjun on 16/3/10
// Copyright © 2016 Wang Longshuai. All rights reserved.
//

#import "LSCircleListViewController.h"
#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleMainPageViewController.h"
#import "LSCircleMainPageViewModel.h"
#import "LSCircleListCollectionCellViewModel.h"
#import "LSNewCircleListViewController.h"

@interface LSCircleListViewController(a)

@property (nonatomic.strong) LSCircleListView *mainView;

@property (nonatomic.strong) LSCircleListViewModel *viewModel;

@end

@implementation LSCircleListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

#pragma mark - system
- (void)updateViewConstraints {

    WS(weakSelf)

    [self.mainView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.edges.equalTo(weakSelf.view);
    }];

    [super updateViewConstraints];
}

#pragma mark - private
- (void)yc_addSubviews {

    [self.view addSubview:self.mainView];
}

- (void)yc_bindViewModel {

    @weakify(self);
    [[self.viewModel.cellClickSubject takeUntil:self.rac_willDeallocSignal] subscribeNext:^(LSCircleListCollectionCellViewModel *viewModel) {

        @strongify(self); LSCircleMainPageViewModel *mainViewModel = [[LSCircleMainPageViewModel alloc] init]; mainViewModel.headerViewModel.circleId = viewModel.idStr; mainViewModel.headerViewModel.headerImageStr = viewModel.headerImageStr; mainViewModel.headerViewModel.title = viewModel.name; mainViewModel.headerViewModel.numStr = viewModel.peopleNum; LSCircleMainPageViewController *circleMainVC = [[LSCircleMainPageViewController alloc] initWithViewModel:mainViewModel];  [self.rdv_tabBarController setTabBarHidden:YES animated:YES];
        [self.navigationController pushViewController:circleMainVC animated:YES];
    }];

    [self.viewModel.listHeaderViewModel.addNewSubject subscribeNext:^(id x) {

        @strongify(self);
        LSNewCircleListViewController *newCircleListVC = [[LSNewCircleListViewController alloc] init];
        [self.rdv_tabBarController setTabBarHidden:YES animated:YES];
        [self.navigationController pushViewController:newCircleListVC animated:YES];
    }];
}

- (void)yc_layoutNavigation {

    self.title = @" Circle list";
    [self.rdv_tabBarController setTabBarHidden:NO animated:YES];
}

#pragma mark - layzLoad
- (LSCircleListView *)mainView {

    if(! _mainView) { _mainView = [[LSCircleListView alloc] initWithViewModel:self.viewModel];
    }

    return _mainView;
}

- (LSCircleListViewModel *)viewModel {

    if(! _viewModel) { _viewModel = [[LSCircleListViewModel alloc] init]; }return _viewModel;
}

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

/* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */

@endCopy the code

For VC, it is divided into three modules, which are as follows:

I The first module: system functions

This function is a new method to update constrained layout in ViewController starting in iOS6.0. The default implementation of this method is to call updateConstraints of the corresponding View. When a ViewController’s View updates its View layout, it first calls the updateViewConstraints method of the ViewController. This method can be overridden to update the internal layout of the current View without having to inherit the View to override the updateConstraints method. When overriding this method, be sure to call super or call the -updateconstraints method of the current View.

ⅱ The second module: private function

The specific functions of these three functions are also mentioned in the base class above, namely

  • Yd_addSubviews: Adds a View to a ViewController

  • Yd_bindViewModel: Two jump events are bound here.

  • Yd_layoutNavigation: set title to “circle list” and TabBar not hidden

Iii Module 3: Lazy loading

I don’t have to explain it, I’ll load it when I need it.

2. View processing

On the first code

//
// LSCircleListView.m
// ZhongShui
//
// Created by Wang Longjun on 16/3/10
// Copyright © 2016 Wang Longshuai. All rights reserved.
//

#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleListHeaderView.h"
#import "LSCircleListSectionHeaderView.h"
#import "LSCircleListTableCell.h"

@interface LSCircleListView()"UITableViewDataSource.UITableViewDelegate>

@property (strong.nonatomic) LSCircleListViewModel *viewModel;

@property (strong.nonatomic) UITableView *mainTableView;

@property (strong.nonatomic) LSCircleListHeaderView *listHeaderView;

@property (strong.nonatomic) LSCircleListSectionHeaderView *sectionHeaderView;

@end
@implementation LSCircleListView


/* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */

#pragma mark - system

- (instancetype)initWithViewModel:(id<YCViewModelProtocol>)viewModel {

    self.viewModel = (LSCircleListViewModel *)viewModel;
    return [super initWithViewModel:viewModel];
}

- (void)updateConstraints {

    WS(weakSelf)
    [self.mainTableView mas_makeConstraints:^(MASConstraintMaker *make) {

        make.edges.equalTo(weakSelf);
    }];
    [super updateConstraints];
}

#pragma mark - private
- (void)yc_setupViews {

    [self addSubview:self.mainTableView];
    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];
}

- (void)yc_bindViewModel {

    [self.viewModel.refreshDataCommand execute:nil];

    @weakify(self);

    [self.viewModel.refreshUI subscribeNext:^(id x) {

        @strongify(self);
        [self.mainTableView reloadData];
    }];

    [self.viewModel.refreshEndSubject subscribeNext:^(id x) {
        @strongify(self);

        [self.mainTableView reloadData];

        switch ([x integerValue]) {
            case LSHeaderRefresh_HasMoreData: {

                [self.mainTableView.mj_header endRefreshing];

                if (self.mainTableView.mj_footer == nil) {

                    self.mainTableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
                        @strongify(self);
                        [self.viewModel.nextPageCommand execute:nil]; }]; }}break;
            case LSHeaderRefresh_HasNoMoreData: {

                [self.mainTableView.mj_header endRefreshing];
                self.mainTableView.mj_footer = nil;
            }
                break;
            case LSFooterRefresh_HasMoreData: {

                [self.mainTableView.mj_header endRefreshing];
                [self.mainTableView.mj_footer resetNoMoreData];
                [self.mainTableView.mj_footer endRefreshing];
            }
                break;
            case LSFooterRefresh_HasNoMoreData: {
                [self.mainTableView.mj_header endRefreshing];
                [self.mainTableView.mj_footer endRefreshingWithNoMoreData];
            }
                break;
            case LSRefreshError: {

                [self.mainTableView.mj_footer endRefreshing];
                [self.mainTableView.mj_header endRefreshing];
            }
                break;

            default:
                break; }}]; }#pragma mark - lazyLoad
- (LSCircleListViewModel *)viewModel {

    if(! _viewModel) { _viewModel = [[LSCircleListViewModel alloc] init]; }return _viewModel;
}

- (UITableView *)mainTableView {

    if(! _mainTableView) { _mainTableView = [[UITableView alloc] init];
        _mainTableView.delegate = self;
        _mainTableView.dataSource = self;
        _mainTableView.backgroundColor = GX_BGCOLOR;
        _mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _mainTableView.tableHeaderView = self.listHeaderView;
        [_mainTableView registerClass:[LSCircleListTableCell class] forCellReuseIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])]];

        WS(weakSelf)
        _mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{

            [weakSelf.viewModel.refreshDataCommand execute:nil];
        }];
        _mainTableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{

            [weakSelf.viewModel.nextPageCommand execute:nil];
        }];
    }

    return _mainTableView;
}

- (LSCircleListHeaderView *)listHeaderView {

    if(! _listHeaderView) { _listHeaderView = [[LSCircleListHeaderView alloc] initWithViewModel:self.viewModel.listHeaderViewModel];
        _listHeaderView.frame = CGRectMake(0.0, SCREEN_WIDTH, 160);
    }

    return _listHeaderView;
}

- (LSCircleListSectionHeaderView *)sectionHeaderView {

    if(! _sectionHeaderView) { _sectionHeaderView = [[LSCircleListSectionHeaderView alloc] initWithViewModel:self.viewModel.sectionHeaderViewModel];
    }

    return _sectionHeaderView;
}

#pragma mark - delegate

#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

    return 1;
}

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

    return self.viewModel.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {


    LSCircleListTableCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])] forIndexPath:indexPath];

    if (self.viewModel.dataArray.count > indexPath.row) {

        cell.viewModel = self.viewModel.dataArray[indexPath.row];
    }

    return cell;
}

#pragma mark - UITableViewDelegate

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return 100;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    if (self.viewModel.dataArray.count > indexPath.row) {

        [self.viewModel.cellClickSubject sendNext:self.viewModel.dataArray[indexPath.row]]; }} - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    return self.sectionHeaderView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {

    return 45;
}

@endCopy the code

The main View is divided into four modules:

I First module: System functions

Each View will have a corresponding ViewModel, so it is easier to reuse, here because it is the main View, generally SPEAKING, I will make VC and main V share a VM, so for jump, data sharing and so on have great benefits.

ⅱ The second module: private function


The specific functions have been marked on the way. It should be noted that the processing of different data is written by me, which is definitely not so rigorous logically. It is only for reference.

Iii Module 3: Lazy loading

There is nothing to say here, just a refresh using the MJRefresh third-party library. However, if you are careful, you will notice that the following two views are configured with VM initialization, which is the same meaning as the main View configuration initialization.

Fourth module: Agents and data sources

The custom Cell is used, and the ViewModel is used to configure. The click event is also linked with the previous VC jump, and the VM is passed.

3. Processing of LSCircleListModel

Again, code first

//
// LSCircleListModel.h
// ZhongShui
//
// Created by Createnext on 16/3/17
// Copyright © 2016 Wang Longshuai. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface LSCircleListModel : NSObject

@property (nonatomic.copy) NSString *idStr;

@property (nonatomic.copy) NSString *title;

@property (nonatomic.copy) NSString *intro;

@property (nonatomic.copy) NSString *img;

@property (nonatomic.copy) NSString *memberCount;

@property (nonatomic.copy) NSString *topicCount;

@endCopy the code
// // LSCircleListModel. M // ZhongShui // // Created by Wang Longshuai on 16/3/17. // Copyright © 2016 Wang Longshuai. All rights reserved. // #import "LSCircleListModel.h" @implementation LSCircleListModel + (NSDictionary *)mj_replacedKeyFromPropertyName { return @{ @"idStr":@"id", @"title":@"title", @"intro":@"intro", @"img":@"img", @"memberCount":@"MemberCount", @"topicCount":@"TopicCount", }; } @endCopy the code

This is a pure data model, using the MJExtention data model transformation framework. I didn’t do any other logic.

4. Processing of ViewModel

//
// LSCircleListViewModel.h
// ZhongShui
//
// Created by Wang Longjun on 16/3/10
// Copyright © 2016 Wang Longshuai. All rights reserved.
//

#import "YCViewModel.h"
#import "LSCircleListHeaderViewModel.h"
#import "LSCircleListSectionHeaderViewModel.h"

@interface LSCircleListViewModel : YCViewModel

@property (nonatomic.strong) RACSubject *refreshEndSubject;

@property (nonatomic.strong) RACSubject *refreshUI;

@property (nonatomic.strong) RACCommand *refreshDataCommand;

@property (nonatomic.strong) RACCommand *nextPageCommand;

@property (nonatomic.strong) LSCircleListHeaderViewModel *listHeaderViewModel;

@property (nonatomic.strong) LSCircleListSectionHeaderViewModel *sectionHeaderViewModel;

@property (nonatomic.strong) NSArray *dataArray;

@property (nonatomic.strong) RACSubject *cellClickSubject;

@endCopy the code
//
//  LSCircleListViewModel.m
//  ZhongShui
//
//  Created by 王隆帅 on 16/3/10.
//  Copyright © 2016年 王隆帅. All rights reserved.
//

#import "LSCircleListViewModel.h"
#import "LSCircleListCollectionCellViewModel.h"
#import "LSCircleListModel.h"

@interface LSCircleListViewModel ()

@property (nonatomic, assign) NSInteger currentPage;

@end

@implementation LSCircleListViewModel

- (void)yc_initialize {

    @weakify(self);
    [self.refreshDataCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) {

        @strongify(self);

        if (dict == nil) {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowErrorStatus(@"网络连接失败");
            return;
        }

        if ([dict[@"status"] integerValue] == 0) {

            self.listHeaderViewModel.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"JoinCircles"]).rac_sequence map:^id(NSDictionary *dic) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                return viewModel;
            }] array] mutableCopy];

            self.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]).rac_sequence map:^id(NSDictionary *dic) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                return viewModel;
            }] array] mutableCopy];


            [self ls_setHeaderRefreshWithArray:dict[@"Circles"]];
            [self ls_dismiss];

        } else {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowMessage(dict[@"mes"]);
        }        
    }];


    [[[self.refreshDataCommand.executing skip:1] take:1] subscribeNext:^(id x) {

        @strongify(self);
        if ([x isEqualToNumber:@(YES)]) {

            [self ls_showWithStatus:@"正在加载"];
        }
    }];

    [self.nextPageCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) {

        @strongify(self);

        if (dict == nil) {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowErrorStatus(@"网络连接失败");
            return;
        }

        if ([dict[@"status"] integerValue] == 0) {

            NSMutableArray *recommandArray = [[NSMutableArray alloc] initWithArray:self.dataArray];
            for (NSDictionary *subDic in [(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]) {

                LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:subDic];
                LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init];
                viewModel.model = model;
                [recommandArray addObject:viewModel];
            }
            self.dataArray = recommandArray;

            [self ls_setFootRefreshWithArray:dict[@"Circles"]];
            [self ls_dismiss];

        } else {

            [self.refreshEndSubject sendNext:@(LSRefreshError)];
            ShowMessage(dict[@"mes"]);
        }
    }];
}

#pragma mark - private

- (NSMutableDictionary *)requestCircleListWithId:(NSString *)idStr currentPage:(NSString *)currentPage {

    idStr = IF_NULL_TO_STRING(idStr);
    currentPage = IF_NULL_TO_STRING(currentPage);

    NSMutableDictionary * dict = [@{@"MemberID": idStr, @"pageSize": LS_REQUEST_LIST_COUNT, @"pageIndex":currentPage} mutableCopy];

    return dict;
}

- (void)ls_setFootRefreshWithArray:(NSArray *)array {

    if (array.count < LS_REQUEST_LIST_NUM_COUNT) {

        [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasNoMoreData)];
    } else {

        [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasMoreData)];
    }
}

- (void)ls_setHeaderRefreshWithArray:(NSArray *)array {

    if (array.count < LS_REQUEST_LIST_NUM_COUNT) {

        [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasNoMoreData)];
    } else {

        [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasMoreData)];
    }
}

#pragma mark - lazyLoad
- (RACSubject *)refreshUI {

    if (!_refreshUI) {

        _refreshUI = [RACSubject subject];
    }

    return _refreshUI;
}

- (RACSubject *)refreshEndSubject {

    if (!_refreshEndSubject) {

        _refreshEndSubject = [RACSubject subject];
    }

    return _refreshEndSubject;
}

- (RACCommand *)refreshDataCommand {

    if (!_refreshDataCommand) {

        @weakify(self);
        _refreshDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

            @strongify(self);
            return [RACSignal createSignal:^RACDisposable *(id subscriber) {

                @strongify(self);
                self.currentPage = 1;
                [self.request POST:LS_URL_CIRCLE_MEMBER_LIST parameters:[self requestCircleListWithId:@"1" currentPage:[NSString stringWithFormat:@"%d",self.currentPage]] success:^(CMRequest *request, NSString *responseString) {

                    NSDictionary *dict = [responseString objectFromJSONString];
                    [subscriber sendNext:dict];
                    [subscriber sendCompleted];

                } failure:^(CMRequest *request, NSError *error) {

                    ShowErrorStatus(@"网络连接失败");
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
        }];
    }

    return _refreshDataCommand;
}

- (RACCommand *)nextPageCommand {

    if (!_nextPageCommand) {

        @weakify(self);
        _nextPageCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {

            @strongify(self);
            return [RACSignal createSignal:^RACDisposable *(id subscriber) {

                @strongify(self);
                self.currentPage ++;
                [self.request POST:LS_URL_CIRCLE_TOPIC_LIST parameters:nil success:^(CMRequest *request, NSString *responseString) {

                    NSDictionary *dict = [responseString objectFromJSONString];
                    [subscriber sendNext:dict];
                    [subscriber sendCompleted];

                } failure:^(CMRequest *request, NSError *error) {

                    @strongify(self);
                    self.currentPage --;
                    ShowErrorStatus(@"网络连接失败");
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
        }];
    }

    return _nextPageCommand;
}

- (LSCircleListHeaderViewModel *)listHeaderViewModel {

    if (!_listHeaderViewModel) {

        _listHeaderViewModel = [[LSCircleListHeaderViewModel alloc] init];
        _listHeaderViewModel.title = @"已加入的圈子";
        _listHeaderViewModel.cellClickSubject = self.cellClickSubject;
    }

    return _listHeaderViewModel;
}

- (LSCircleListSectionHeaderViewModel *)sectionHeaderViewModel {

    if (!_sectionHeaderViewModel) {

        _sectionHeaderViewModel = [[LSCircleListSectionHeaderViewModel alloc] init];
        _sectionHeaderViewModel.title = @"推荐圈子";
    }

    return _sectionHeaderViewModel;
}

- (NSArray *)dataArray {

    if (!_dataArray) {

        _dataArray = [[NSArray alloc] init];
    }

    return _dataArray;
}

- (RACSubject *)cellClickSubject {

    if (!_cellClickSubject) {

        _cellClickSubject = [RACSubject subject];
    }

    return _cellClickSubject;
}

@endCopy the code

ViewModel is also divided into three modules, because the code is too much to talk about important

ⅰ The first module: processing data, logic module

To process the data piece, first use the dictionary to Model, then use Model to configure the ViewModel, ViewModel to correspond to the UI and its logic.

ⅱ The second module: private function

For the request parameter dictionary, it can be placed in the VM for modular portability, or it can be placed in the public API for management, depending on the individual choice, there is no absolute good location, only more suitable for the individual location.

The other two functions are private functions that handle pull-down and pull-up with no more data.

Iii Module 3: Lazy loading

This data request uses a utility class that AFN rewraps, which is essentially AFN.

5. Simplify the APPDelegate code

In general, we’ll see a lot of things in formal projects that need to be loaded as soon as we start the project, so soon the APPDelegate gets bigger and bigger. Now that the rest of the code is simplified and decoupled, we can do that too.

The directory is as follows:

The simplified AppDelegate looks like this:

Other code locations are as follows:

When a class object is introduced into a project, the Runtime sends a Load message to each class object. The Load method is also magic because it is called only once for each class or even a class when it is introduced, in order that the parent class takes precedence over the subclass, and the subclass takes precedence over the class. And the load method is not automatically inherited by the class, so the load method in each class does not need to call the parent class’s method as the viewDidLoad method does.

This is taking advantage of this dark magic thing, haha, and simplifying the APPDelegate!

Four, afterword.

Originally thoughtTake out the base classAgain,Want to use Category + Protocol and use Runtime Methode Swizzle to add their own private functions to system functionsVC is already done, but it turns out it’s too big, you make a Category for VC, UINavigationController is affected, if you make a Category for View, other inheritors of View are affected, And at that time, the exchange methods were all in one Category, to the second covered… I don’t know why, I didn’t go ahead because I knew it was a dead end…

Writing here, you should have some understanding of the design mode under my pen, because it involves too many things, mainly these things need to stand on the shoulders of giants, encountered in the article and do not understand:

Ha ha ha! Don’t blame me… Not that I’m unresponsible, as you can see that the size of the file is quite a bit excessive, and I feel like I’ve managed to cover all the details of the photoshoot (乛◡乛 danjun)! If you have any doubts, we can communicate in the comments section!

Finally, I really hope that you can point out the shortcomings and make progress together!

This article is prepared by the author Wang Longshuai, reprint please retain the copyright website, thank you for your understanding and sharing, let life become better!