zoukankan      html  css  js  c++  java
  • 【iOS】小项目框架设计(ReactiveCocoa+MVVM+AFNetworking+FMDB)

    网址: http://www.saitjr.com/ios/ios-framework-reactivecocoa-mvvmafnetworking-fmdb.html

    上一个项目使用到了ReactiveCocoa+MVVM+AFNetworking+FMDB框架设计,从最初的尝试,到后来不断思考和学习,现在对这样一个整体设计还是有了一定了理解与心得。在此与大家分享下。

    本文将不再过多的描述ReactiveCocoaMVVMFMDB的使用细节。关于ReactiveCocoa,我有一篇实用案例的博客:

    http://www.brighttj.com/ios/ios-reactivecocoa-utility-demo.html

    文章介绍的更多的是我对这个框架设计的理解,而不是具体代码逻辑的讲解。关于代码逻辑,我会在Demo中给出详细的注释,本文Demo下载地址:

    https://github.com/saitjr/ReactiveCocoa-MVVM-AFNetworking-FMDB.git

    环境信息:

    Mac OS X 10.11

    Xcode 7.0.1

    iOS 9.0.1

    ReactiveCocoa 2.4.7

    AFNetworking 2.6.1

    FMDB 2.5

    MJExtension 2.5.14


    正文

    工程目录

    先来谈谈工程目录吧,如图:

    工程目录

    工程目录

    1. 【Module】+【Model】

    在这个目录中,比较核心的是【Module】与【Model】,他们组成了整个MVVM框架。

    【Module】与【Model】均包含【Base】,其中有BaseModelBaseViewModelBaseViewController。在开发中,我还是习惯无论是否需要基类,都去写一个。难免开发之初就考虑到,也难免之后需求会变更。

    2. 【Interface】接口

    这是借鉴了Java中的接口思想,目的是为了统一方法名。例如里面的SQLInterface.h文件,就是一个对数据进行CRUD操作的protocol,并且可以规定里面的方法是否必须实现。

    3. 【Configuration】配置

    对项目的一些基本配置,如基本宏定义、常量、通知名,亦或是Cell的identifier。宏定义中一般包含项目基本属性,如:主色调、常用方法等。

    在提供的Demo中,我将SQL语句放在了SQL.h中,是因为SQL只有一个文件在引用,其中的定义方式是:

    static NSString * const selectArticleSQL = @"SELECT * FROM article";
    

    NotificationNames.h会在大部分文件中用到,所以使用UIKIT_EXTERN定义为了全局变量:

    -----.h
    UIKIT_EXTERN NSString * const LoadAllNotification;
    -----.m
    NSString * const LoadAllNotification = @"LoadAllNotification";
    

    5. 【Category】类目

    项目中没有打包的类目,例如给三方或系统类新增的一些方法。

    RAC+MVVM

    RAC+MVVM在【Module】和【Model】这两个目录中进行实现。在这之中,MVVM是框架思想,RAC只是辅助而已。

    一、MVVM

    之所以采用MVVM,而不是MVC,也得益于MVVM的一大特点,就是减轻C层的负担,毕竟以前的C层完全就是百宝箱,什么样的代码都写在里面。

    对于MVVM我的理解和拆分是这样的:

    1. Model与View

    这一层和MVC中的Model、View含义相同。

    2. ViewModel

    这一层主要作用是将以前写在ViewController中的数据处理放在ViewModel中,如:网络请求、数据缓存、无法直接展示的数据处理(如NSNumber这类的,就在VM中处理成NSString,然后V层直接用,而不是在V层中处理)。

    二、Demo中VC的设计

    图形结合源码应该能看个大概。

    VC设计

    VC设计

    三、自定义cell的设计(可延伸至自定义View的设计)

    因为介绍的是VC,所以这里再单独说一下HomePageCell这个自定义cell的设计。

    因为想到实际项目中,可能会有比较复杂的cell,所以Demo中写的是一个比较完整的设计方式(如果单单看这个Demo的话,这个自定义cell太简单,没必要有一个单独的VM,有点过度设计)。

    cell中的构思是,cell有一个CellVM来管理cell中要显示的数据,CellVM来自于VC中,dataSource数组。处理方式具体是:在网络请求完成以后,将字典->模型,然后通过模型,初始化CellVM,然后将CellVM放入dataSource数组。

    Cell实现部分代码如下:

    @implementation HomePageCell
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        
        [self setupSignal];
    }
    
    - (void)setupSignal {
        
        @weakify(self);
        [RACObserve(self, viewModel) subscribeNext:^(HomePageCellViewModel *viewModel) {
            
            @strongify(self);
            self.textLabel.text = viewModel.titleText;
            self.detailTextLabel.text = viewModel.authorText;
        }];
    }
    
    @end
    

    AFNetworking

    前几天,AFNetworking升级到了3.0。将以前基于NSURLConnection的API,全都改成了NSURLSession,关于更新的详情,可以看AFNetworking 3.0迁移指南这篇文章。

    在页面销毁、重新请求等情况下,需要将还在队列中的请求取消,以免占用资源。

    考虑到这一点,结合我的网络请求在VM发送,权衡再三,在BaseViewModel的基础上,再进行了一次封装——RequestViewModel。这个VM有AFHTTPSessionManager类的属性,一个该属性的懒加载和一个在dealloc中取消请求方法。

    在以前使用MVC的时候,我会对AFNetworking进行再次的封装,这样更像是一个MVCS的设计,目的是防止VC过重,现在把这部分代码扔在了VM中,看起来还好,所以并没有对AFNetworking再次封装。关于以前的设计方式,可以看这篇文章:

    网络请求框架封装

    FMDB

    FMDB提供了一种线程安全的模式,在这之中维护这一个串行队列。

    1. 初始化

    初始化方式参考了开源项目MVVMReactiveCocoa,作者采用了类目的形式给了一个单例:

    @implementation FMDatabaseQueue (Extension)
    
    + (instancetype)shareInstense {
        
        static FMDatabaseQueue *queue = nil;
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            queue = [FMDatabaseQueue databaseQueueWithPath:DB_PATH];
        });
        
        return queue;
    }
    
    @end
    

    2. 统一接口

    关于CRUD方法的定义,借鉴了Java Interface的设计:

    3. SQL语句

    根据实际情况,SQL语句参数可以采用?的形式,也可以采用:keyword的形式。

    INSERT INTO myTable VALUES (?, ?, ?)
    INSERT INTO myTable VALUES (:id, :name, :value)
    

    关于数据库的创建与更新,我并不建议在Bundle中包含xxx.sql这样的文件,因为他们会在编译后一起打包,用户下载解压就能看到,并不怎么安全。目前我是直接在程序中写的SQL,不知道大家有没有更好的方式。

    4. CRUD

    在网络请求成功的时候,存入数据。存储是一个批量的操作,建议采用事务inTransaction实现。简单的操作采用inDatabase即可。

    FMDatabaseQueue是一个串行队列,并且inTransactioninDatabase都是同步线程,需要注意的是不要在block中执行另一个数据库访问操作,防止线程死锁

    最后

    每个项目完后,都会有很多收获,有很多东西需要整理总结。写这篇博客的原因有两个:

    原因之一:因为我在开发过程中踩了不少坑,可能开发到中途,发现框架设计不好。框架如何设计,并没有一个标准答案,而且设计思想,还需要在不断实践中得出,所以每次总结,是为了给自己看,也是为了帮到其他有同样困扰的朋友。

    原因之二:也正是因为我不知道框架到底怎么样,所以写出来,让大家看看,都多多提出建议。谢谢。

  • 相关阅读:
    【Codeforces 349B】Color the Fence
    【Codeforces 459D】Pashmak and Parmida's problem
    【Codeforces 467C】George and Job
    【Codeforces 161D】Distance in Tree
    【Codeforces 522A】Reposts
    【Codeforces 225C】Barcode
    【Codeforces 446A】DZY Loves Sequences
    【Codeforces 429B】Working out
    【Codeforces 478C】Table Decorations
    【Codeforces 478C】Table Decorations
  • 原文地址:https://www.cnblogs.com/DMDD/p/5046003.html
Copyright © 2011-2022 走看看