美文网首页
架构设计文档

架构设计文档

作者: 德惟 | 来源:发表于2021-12-23 10:13 被阅读0次

    架构设计文档

    [TOC]

    1、技术选型

    1.1 开发语言

    学习成本 开发效率 开源支持 稳定性 前景
    Objective- C 2007年发布Objective-C 2.0,拥有大量的网络学习资源。 语法冗余,面向对象编程,易维护,沟通成本低 拥有众多成熟第三方开源库 API稳定 已经趋于稳定,很少变化
    Swift 2014年发布,开源,入门容易精通难 语法简洁,运行效率高,支持面向对象编程,面向协议编程,声明式编程,函数式编程,泛型编程 成熟第三方库明显少于Objective- C API不会再次出现大规模变化,不向前兼容 新兴语言,结合了众多现代语言的优点,Apple推荐语言

    综上所述,易维护,沟通成本低,我们选用已经成熟 Objective-C 语言作为首选开发语言。

    1.2 代码仓库

    这里有一篇很好的日常实践对比why-git。总结一下就是Git对比SVN

    • 方便的分支切换、版本回滚
    • 更好地规范化版本日志
    • 更多开源免费的配套设施(如Gitlab)
    • 适合实行 Code Review
    • 规范化开发流程 Git-Flow

    如果你还不熟悉Git的相关操作,可以在这里学习。

    1.3 开源项目管理

    开源库的管理决定使用CocoaPods。如果没有安装的同学可以按照如下方法安装。

    # 列出gem源 gem sources -l 
    # 移除 ruby 源,因为要翻墙访问 gem sources --remove https://rubygems.org/ 
    # 添加 taobao 源 gem sources -a https://ruby.taobao.org/ 
    # 可以不更新 gem update --system 
    # 安装 cocoapods sudo gem install cocoapods 
    # 如果上述安装不能成功,因为是mac系统在10.10后不允许修改 /usr/bin 目录,需要改变安装目录。 
    # sudo gem install -n /usr/local/bin cocoapods
    

    目前使用 CocoaPods 集成的一些开源仓库

    仓库名称 用途 选型理由
    AFNetworking 网络请求 AFNetworking是封装的NSURLSession的网络请求,由五个模块组成:分别由网络通信(核心),网络通讯安全策略,网络状态监听,网络通信信息序列化和反序列化,UIKit的扩展库
    Reachability 网络状态判断 Reachability类是Apple官方出的判断当前网络状况的工具类,这个库一直在随着iOS的版本在更新,目前iOS10对应的最新版本是5.0
    YTKNetwork 对AFNetworking的封装 YTKNetwork 的基本的思想是把每一个网络请求封装成对象。所以使用 YTKNetwork,你的每一个请求都需要继承 YTKRequest 类,通过覆盖父类的一些方法来构造指定的网络请求
    SDWebImage 图片异步加载和缓存 SDWebImage具有缓存支持的异步映像下载程序。并添加了像UI元素分类类UIImageView、UIButton、MKAnnotationView,可以直接为这些UI元素添加图片。
    Masonry 布局框架 是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有更高的可读性。
    IQKeyboardManager 键盘管理工具 解决弹起键盘遮盖输入框的问题
    YYKit iOS开发组件 以下是项目中常用到的几个组件
    YYCategories 常用分类 Foundation and UIKit提供许多有用的分类
    YYText 富文本 强大的iOS富文本组件
    YYModel 字典转模型 高性能的字典转模型的框架
    YYImage 图片加载 功能强大的图像框架
    YYWebImage 图片加载 异步图片加载框架
    YYCache 缓存框架 高性能 iOS 缓存框架,提供内存缓存磁盘缓存
    CHTCollectionViewWaterfallLayout 瀑布流
    UICollectionViewLeftAlignedLayout 使collectionView左对齐 UICollectionViewLeftAlignedLayout是第三方的左对齐布局管理类,其继承自UICollectionViewFlowLayout,使用其可以方便的进行左对齐的瀑布流界面布局。
    UITableView+FDTemplateLayoutCell cell高度 自动计算cell高度并缓存cell高度
    TABAnimated 空数据填充 tableView骨架屏
    FDFullscreenPopGesture 全屏左滑pop手势 提供全屏手势返回功能
    FMDB SQLite数据库 提供线程安全的Sqlite数据库存取操作
    MJExtension 字典转模型框架 一套字典和模型之间互相转换的超轻量级框架
    MJRefresh 下拉刷新和上拉加载控件 用于为应用添加常用的上拉加载更多与下拉刷新效果,适用 UIScrollView、UITableView、UICollectionView、UIWebView
    pop 动画过渡 动画引擎,用于动画过渡。可以参照popping
    DZNEmptyDataSet 空白页 UITableView/UICollectionView数据内容为空时展示的空白页
    MBProgressHUD 蒙版 加载loading以及显示提示蒙版的HUD
    SVProgressHUD 蒙版 加载loading
    JPFPSStatus 帧数检测 通过FPS(Frames Per Second)每秒传输帧数的高低来检查列表滚动的流畅度
    TZImagePickerController 图片选择器 总体上跟微信的照片选择器界面和功能都差不多一样
    PNChart 各种图表的展示 PNChart 是一个强大的带动画的图表库
    Charts 图表 Charts是一个轻量级的简易图表,主要为DataV大屏数据展示组件库 提供图表支持,在该场景下不考虑图表交互,仅需展示效果,因此插件不提供交互及复杂功能。插件配置项参考eCharts,具有相关经验则极易上手使用
    MMDrawerController 侧边栏 侧边栏的 Controller,实现抽屉效果
    RESideMenu 侧边栏 QQ 侧边栏的效果
    JSQMessagesViewController 推送 聊天对话
    CYLTabBarController 低耦合集成TabBarController CYLTabBarController 是一个自定义的TabBarController, 集成非常简单
    TTTAttributedLabel 富文本的Label TTTAttributedLabel 继承于 UILabel,所以具有 UILabel 所有的属性和方法。通过CoreText绘制富文本
    JVFloatLabeledTextField 特殊效果的textField 浮动文字的输入框
    SDCycleScrollView 循环轮播 采用UICollectionView的重用机制和循环滚动的方式实现图片的轮播滚动
    iCarousel 轮播 iCarousel是一个类,它继承于UIView,用于简化实现各种类型的旋转木马(分页滚动视图)iPhone、iPad和Mac OS。iCarousel实现一些常见的影响如圆柱、平面式的旋转木马
    PDTSimpleCalendar 日历 PDTSimpleCalendar是一个简单的日历/日期选择器组件,基于UICollectionView。
    LBXScan 二维码 二维码相关,ZXing、ZBar、iOS系统AVFoundation扫码封装,可自行选择
    FLEX 强大的调试库 FLEX是一个需要注入式的一种框架,从描述来看,功能非常多。主要来讲的话能够对正在运行的应用进行样式的修改和控件的读取
    UICKeyChainStore 存放用户账号密码组件 在app开发过程中通常会涉及到敏感信息的保存,ios给我们提供了keychain来将数据保存到钥匙串中
    XHLaunchAd 广告页 1.支持全屏/半屏广告.2.支持静态/动态广告.3.兼容iPhone和iPad.4.支持广告点击事件5.自带图片下载,缓存功能.6.支持设置未检测到广告数据,启动页停留时间7.无依赖其他第三方框架
    MLeaksFinder 内存泄漏检测 支持自动检测代码中存在的内存泄漏问题,只在 Debug 模式下有效
    OpenShare 社交分享库 无需引入众多第三方社交文件即可实现社交分享功能,实际大小几KB左右,降低包体积
    NJKWebViewProgress Web加载进度显示 可对UIWebView提供真实的加载进度显示
    BlocksKit 事件回调 针对界面事件响应行为提供便捷的Block方式回调处理,方便集中处理逻辑
    FXBlurView 视图模糊库 提供实时的模糊效果
    CocoaLumberjack 日志库 提供日记分级别记录,日志文件存储,日志格式化输出等

    1.4 可持续集成方案

    可持续集成选用业界成熟的Jenkins作为可持续集成工具.

    Jenkins 是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,专注于更为重要的业务逻辑
    实现上。同时 Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图表的形式形象地展示项目构建的
    趋势和稳定性.

    • 支持插件扩展
    • 自定义执行脚本
    • 自动构建
    • 自动部署
    • 错误反馈

    2、系统架构

    2.1 总体设计

    图片.png
    (evernotecid://0C306E66-8F16-4830-9306-563B0445D83E/appyinxiangcom/29976633/ENResource/p6)

    APP总体分为四大块

    • 基础组件:提供网络请求,数据缓存,日志等基础服务功能,同业务逻辑分离.
    • 中转器:提供组件间统一调用功能.
    • 业务组件:各种不同业务组件共同组成的集合,业务组件间相互独立.

    2.2 组件化设计

    Router就像是个调度中心,各个模块通过路由调度其他模块,模块之间不需要相互引用,调度方式更加统一,更加自由,能够实现解耦的作用,同时也为之后的组件化开发提供了基础。

    图片.png

    JLRoutes 的问题主要在于查找 URL 的实现不够高效,通过遍历而不是匹配。还有就是功能偏多。
    HHRouter 的 URL 查找是基于匹配,所以会更高效,MGJRouter 也是采用的这种方法,但它跟 ViewController 绑定地过于紧密,一定程度上降低了灵活性。
    于是就有了 MGJRouter

    2.2.1 MGJRouter最基本的使用
    [MGJRouter registerURLPattern:@"mgj://foo/bar" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"routerParameterUserInfo:%@", routerParameters[MGJRouterParameterUserInfo]);
    }];
    [MGJRouter openURL:@"mgj://foo/bar"];
    
    2.2.2 当匹配到 URL 后,routerParameters 会自带几个 key
    extern NSString *const MGJRouterParameterURL;
    extern NSString *const MGJRouterParameterCompletion;
    extern NSString *const MGJRouterParameterUserInfo;
    
    2.2.3 处理中文也没有问题
    [MGJRouter registerURLPattern:@"mgj://category/家居" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"routerParameters:%@", routerParameters);
    }];
    [MGJRouter openURL:@"mgj://category/家居"];
    
    2.2.4 Open时,可以传一些userinfo过去
    [MGJRouter registerURLPattern:@"mgj://category/travel" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"routerParameters[MGJRouterParameterUserInfo]:%@", routerParameters[MGJRouterParameterUserInfo]);
        // @{@"user_id": @1900}
    }];
    
    [MGJRouter openURL:@"mgj://category/travel" withUserInfo:@{@"user_id": @1900} completion:nil];
    
    2.2.5 如果有可变参数(包括 URL Query Parameter)会被自动解析
    [MGJRouter registerURLPattern:@"mgj://search/:query" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"routerParameters[query]:%@", routerParameters[@"query"]); // bicycle
        NSLog(@"routerParameters[color]:%@", routerParameters[@"color"]); // red
    }];
    
    [MGJRouter openURL:@"mgj://search/bicycle?color=red"];
    
    2.2.6 定义一个全局的 URL Pattern 作为 Fallback
    [MGJRouter registerURLPattern:@"mgj://" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"没有人处理该 URL,就只能 fallback 到这里了");
    }];
        
    [MGJRouter openURL:@"mgj://search/travel/china?has_travelled=0"];
    
    2.2.7 当 Open 结束时,执行 Completion Block
    [MGJRouter registerURLPattern:@"mgj://detail" toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"匹配到了 url, 一会会执行 Completion Block");
        
        // 模拟 push 一个 VC
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            void (^completion)() = routerParameters[MGJRouterParameterCompletion];
            if (completion) {
                completion();
            }
        });
    }];
    
    [MGJRouter openURL:@"mgj://detail" withUserInfo:nil completion:^{
        [self appendLog:@"Open 结束,我是 Completion Block"];
    }];
    
    2.2.8 生成 URL

    URL 的处理一不小心,就容易散落在项目的各个角落,不容易管理。比如注册时的 pattern 是 mgj://beauty/:id,然后 open 时就是 mgj://beauty/123,这样到时候 url 有改动,处理起来就会很麻烦,不好统一管理。

    所以 MGJRouter 提供了一个类方法来处理这个问题。

    + (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters;
    

    使用方式

    #define TEMPLATE_URL @"mgj://search/:keyword"
        
    [MGJRouter registerURLPattern:TEMPLATE_URL  toHandler:^(NSDictionary *routerParameters) {
        NSLog(@"routerParameters[keyword]:%@", routerParameters[@"keyword"]); // Hangzhou
    }];
    
    [MGJRouter openURL:[MGJRouter generateURLWithPattern:TEMPLATE_URL parameters:@[@"Hangzhou"]]];
    }
    

    这样就可以在一个地方定义所有的 URL Pattern,使用时,用这个方法生成 URL 就行了。

    2.3 基础服务设计

    基础功能服务包括缓存,数据库,网络请求,下载组件等,编写基础服务时,需要遵循如下几个原则:

    • 引用第三方库必须提供上层封装
    • 对外提供接口尽量简明,并做好对应功能注释
    • 基础服务应该是独立的,尽量减少对外耦合

    2.4 业务层次设计

    架构
    • 界面层 (MVC、MVP、MVVM)
    • 业务层
    • 网络层
    • 本地数据层

    其中,界面层的架构模式又分为好多种,从最开始的MVC模式,演化到MVP,然后到现在的MVVM模式,在不断的演化过程中核心思想归根结底还是:降低各组件之间的耦合度,使得数据的流向更加清晰明了。

    图片.png
    MVVM(Model、View、ViewModel)

    MVVM也是对Controller进行瘦身的一种策略,将业务逻辑放到ViewModel中去处理,比如发送网络请求,加载网络数据进行字典转模型操作等等。


    图片.png
    简单的示例代码如下
    • HomeVC类:负责创建和显示视图
    // HomeVC.m
    @interface HomeVC ()
    @property (nonatomic, strong) UITableView *homeTableView;
    @property (nonatomic, strong) HomeVM *homeVM;
    @end
    
    @implementation HomeVC
    
    #pragma mark - <Life Cycle>
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // RAC网络请求
        @weakify(self);
        [[self.homeVM.homeCommand execute:nil] subscribeNext:^(id  _Nullable x) {
            @strongify(self);
            NSLog(@"currentThread = %@", [NSThread currentThread]);
            //NSLog(@"x = %@", x);
            [self.homeTableView reloadData];
        }];
        
        // ViewModel数据绑定到view上
        [self.homeVM bindVMWithView:self.homeTableView];
        
        // 订阅按钮事件
        [self.homeVM.cellButtonEventSignal subscribeNext:^(UIButton *x) {
            NSLog(@"x = %@", [x titleForState:UIControlStateNormal]);
        }];
        
    }
    
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    #pragma mark - <Override>
    - (void)createSubViews {
        [super createSubViews];
        [self.view addSubview:self.homeTableView];
    }
    
    - (void)addConstraints {
        [super addConstraints];
        [self.homeTableView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self.view.mas_safeAreaLayoutGuideTop);
            make.left.right.bottom.mas_equalTo(self.view);
        }];
    }
    
    #pragma mark - <getter/setter>
    - (HomeVM *)homeVM {
        if (_homeVM == nil) {
            _homeVM = [[HomeVM alloc] init];
        }
        return _homeVM;
    }
    
    - (UITableView *)homeTableView {
        if (_homeTableView == nil) {
            _homeTableView = [[UITableView alloc] init];
            _homeTableView.rowHeight = 120;
            _homeTableView.tableFooterView = [[UIView alloc] init];
        }
        return _homeTableView;
    }
    
    @end
    
    
    • HomeVM类:负责homeTableView的显示和业务逻辑处理
    // HomeVM.h
    @interface HomeVM : NSObject<ViewModelProtocol> {
        RACCommand *_homeCommand;
    }
    
    /// 网络请求RACCommand
    @property (nonatomic, strong, readonly) RACCommand *homeCommand;
    /// 按钮点击信号
    @property (nonatomic, strong, readonly) RACReplaySubject *cellButtonEventSignal;
    
    @end
    
    
    // HomeVM.m
    static NSString *HomeTableCellID = @"HomeTableCellID";
    
    @interface HomeVM ()<UITableViewDataSource, UITableViewDelegate>
    
    @property (nonatomic, copy) NSArray *homeCellVMs;
    @property (nonatomic, strong) HomeTableCellVM *cellVM;
    
    @end
    
    @implementation HomeVM {
        RACReplaySubject *_cellButtonEventSignal;
    }
    
    - (RACCommand *)homeCommand {
        if (_homeCommand == nil) {
            _homeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
                // RACCommand的block
                return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                    // RACSignal的block
                    
                    NSString *homeURL = [XMGNetworkManager urlWithHome];
                    NSDictionary *homeParameters = [XMGNetworkManager paramWithHome];
                    
                    [XMGHttpManager POST:homeURL parameters:homeParameters progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
                        NSLog(@"responseObject = %@", responseObject);
                        int code = [[responseObject objectForKey:@"code"] intValue];
                        if (code == 0) {
                            // 推荐列表
                            NSDictionary *recommends = [[responseObject objectForKey:@"result"] objectForKey:@"recommends"];
                            NSArray *courses = [recommends objectForKey:@"courses"];
                            courses = [[courses.rac_sequence map:^id _Nullable(id  _Nullable value) {
                                HomeModel *model = [HomeModel yy_modelWithJSON:value];
                                HomeTableCellVM *cellVM = [[HomeTableCellVM alloc] init];
                                cellVM.model = model;
                                return cellVM;
                            }] array];
                            // dic->model完成
                            self.homeCellVMs = courses;
                            [subscriber sendNext:courses];
                            [subscriber sendCompleted];
                        }
                    } failure:^(NSURLSessionDataTask *task, NSError *error) {
                        [subscriber sendError:error];
                    }];
                    
                    
                    /*
                    [LFNetManager requestWithAFURL:homeURL httpMethod:Request_Type_POST params:homeParameters success:^(id result, int code) {
                        
                        if (code == 0) {
                            // 推荐列表
                            NSDictionary *recommends = [[result objectForKey:@"result"] objectForKey:@"recommends"];
                            NSArray *courses = [recommends objectForKey:@"courses"];
                            courses = [[courses.rac_sequence map:^id _Nullable(id  _Nullable value) {
                                HomeModel *model = [HomeModel yy_modelWithJSON:value];
                                return model;
                            }] array];
                            // dic->model完成
                            [subscriber sendNext:courses];
                            [subscriber sendCompleted];
                        }
                        
                    } failure:^(NSError *err) {
                        [subscriber sendError:err];
                    }];
                    */
                    
    //                [HomeServer loadHomeList:homeParameters success:^(id  _Nonnull response, int code) {
    //                    
    //                } failure:^(NSError * _Nonnull error) {
    //                    
    //                }];
                    
                    return nil;
                }];
            }];
        }
        return _homeCommand;
    }
    
    #pragma mark - <ViewModelProtocol>
    - (void)bindVMWithView:(UIView *)view {
        UITableView *tableView = (UITableView *)view;
        tableView.dataSource = self;
        tableView.delegate = self;
        
        [tableView registerClass:[HomeTableCell class] forCellReuseIdentifier:HomeTableCellID];
    }
    
    #pragma mark - <UITableViewDataSource/UITableViewDelegate>
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return self.homeCellVMs.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        HomeTableCell *cell = [tableView dequeueReusableCellWithIdentifier:HomeTableCellID forIndexPath:indexPath];
        HomeTableCellVM *cellVM = self.homeCellVMs[indexPath.row];
        [cellVM bindVMWithView:cell];
        [cell.button addTarget:self action:@selector(cellButtonAction:) forControlEvents:UIControlEventTouchUpInside];
        return cell;
    }
    
    #pragma mark - <Event>
    - (void)cellButtonAction:(UIButton *)sender {
        [self.cellButtonEventSignal sendNext:sender];
        //[self.cellButtonEventSignal sendCompleted];
    }
    
    #pragma mark - <getter/setter>
    - (RACReplaySubject *)cellButtonEventSignal {
        if (_cellButtonEventSignal == nil) {
            _cellButtonEventSignal = [RACReplaySubject subject];
        }
        return _cellButtonEventSignal;
    }
    
    @end
    
    
    • HomeTableCellVM类:负责Cell的显示和业务逻辑处理
    // HomeTableCellVM.h
    @interface HomeTableCellVM : NSObject<ViewModelProtocol>
    @property (nonatomic, strong) HomeModel *model;
    @end
    
    // HomeTableCellVM.m
    @implementation HomeTableCellVM
    
    #pragma mark - <ViewModelProtocol>
    - (void)bindVMWithView:(UIView *)view {
        HomeTableCell *cell = (HomeTableCell *)view;
        
        [cell.imgView sd_setImageWithURL:[NSURL URLWithString:self.model.courseImage]];
        cell.titleLabel.text = self.model.courseName;
        cell.subTitleLabel.text = self.model.grade;
        [cell.button setTitle:self.model.grade forState:UIControlStateNormal];
    }
    
    @end
    
    
    

    3、架构目录

    项目.png

    3.1 架构优化方案及内容

    暂无

    3.2 文档与规范

    详见 iOS代码规范

    3.3、架构优化方向及内容

    暂无

    4、优化计划

    暂无

    5、优化结果验证

    暂无

    相关文章

      网友评论

          本文标题:架构设计文档

          本文链接:https://www.haomeiwen.com/subject/knlpfrtx.html