首页 > 代码库 > 模块化开发iOSApp

模块化开发iOSApp

一个iOS菜菜的白话文记录

不停的写博客不是为了炫耀什么,仅仅只是为了个人的一些学习总结,没有过多的什么意思,因为很多东西都能够在网络上找到。如Blog标题我只是一个iOS入门级菜鸟。只有当你的基础足够的扎实时候,才能像YYKit作者那样对iOS平台技术有如此深厚的理解。

模块化开发iOSApp一

FEB 26TH, 2016 11:45 AM

一直想学习下模块化开发一个App

  • 让一个App分成n个单独的模块

  • 每一个插件模块由一个小组单独开发,最大限度解耦

    • 每一个小组只负责该插件模块的开发、代码合并
    • 不用再担心整个App工程的代码合并,也就不再需要对.Xcodeproj解决代码冲突
    • 更重要的是不会因为所有的业务揉在一起,导致对全局业务不熟悉的人修改代码时,还需要额外的看懂其他不相关的业务代码
  • 解耦到极致后,既可以实现每一个模块的热更新

    • 主App只是一个承载众多个插件模块的容器载体
    • 提供一些基础功能
    • 调用其他插件模块完成其对于业务
    • 插件A与插件B之间的沟通

模块化基础、从抽象隔离开始

  • 假设在MainViewController要push到一个显示新闻列表的NewsListViewController
12345678910111213141516
#import "MainViewController.h"//导入具体控制器类#import "NewsListViewController.h"@implementation MainViewController- (void)viewDidLoad {    [super viewDidLoad];    //此时需要显示新闻列表的界面    NewsListViewController *vc = [[NewsListViewController alloc] init];    [self.navigationController pushViewController:vc animated:YES];}@end
  • 改进上述MainViewController直接依赖NewsListViewController的方法

    • 使用抽象接口隔离
      • 同一层依赖接口
      • 各层之间也依赖接口
      • 使用容器管理接口与实现类的映射关系,可配置化
    • 使用一个标识符来代替具体的ViewController类
      • BaseViewController pushWithControllerId: 一个NSIntger枚举值
      • 模拟URL Scheme跳转App的思路

抽象接口 + 容器注入

  • 抽象接口定义:显示新闻列表
123456789101112131415
////  NewsListProtocol.h//  Demo////  Created by xiongzenghui on 16/2/26.//  Copyright © 2016年 xiongzenghui. All rights reserved.//#import <Foundation/Foundation.h>@protocol NewsListProtocol <NSObject>- (void)showNewsList;@end
  • 接口实现类,具体怎么样显示新闻列表
1234567891011121314151617
////  NewsListViewController.h//  Demo////  Created by xiongzenghui on 16/2/26.//  Copyright © 2016年 xiongzenghui. All rights reserved.//#import <UIKit/UIKit.h>//导入接口#import "NewsListProtocol.h"//实现接口,具备显示NewLsit的能力@interface NewsListViewController : UIViewController <NewsListProtocol>@end
123456789101112131415161718192021
////  NewsListViewController.m//  Demo////  Created by xiongzenghui on 16/2/26.//  Copyright © 2016年 xiongzenghui. All rights reserved.//#import "NewsListViewController.h"@implementation NewsListViewController- (void)showNewsList {    //1. 请求服务器    //...    //2. 异步刷新UI显示    //[self.tableView reload];}@end
  • MainViewController依赖一个容器获取接口实现类对象
123456789101112131415161718192021222324252627282930313233343536
////  MainViewController.m//  Demo////  Created by xiongzenghui on 16/2/26.//  Copyright © 2016年 xiongzenghui. All rights reserved.//#import "MainViewController.h"//导入具体控制器类//#import "NewsListViewController.h"//导入抽象接口#import "NewsListProtocol.h"//导入容器#import "Objectiontor.h"@implementation MainViewController- (void)viewDidLoad {    [super viewDidLoad];    //此时需要显示新闻列表的界面    //依赖具体类//    NewsListViewController *vc = [[NewsListViewController alloc] init];//    [self.navigationController pushViewController:vc animated:YES];    //涕泪抽象    id<NewsListProtocol> vc = [Objectiontor objectWithProtocol:@protocol(NewsListProtocol)];    [self.navigationController pushViewController:(UIViewController *)vc animated:YES];}@end
  • 中间容器,只是随便写的例子
123456789101112131415
////  Objectiontor.h//  Demo////  Created by xiongzenghui on 16/2/26.//  Copyright © 2016年 xiongzenghui. All rights reserved.//#import <Foundation/Foundation.h>@interface Objectiontor : NSObject+ (id)objectWithProtocol:(Protocol *)protocol;@end
1
实现部分没写,可参考`Objection`

这样的话就能够做到:

  • 那天想切换新闻显示列表的控制器,直接修改容器中接口配置的实现类
  • 需要单独抽出MainViewController到其他工程,因为没有任何的具体类依赖

还有之前使用类似URL Scheme来替代 NewsListViewController

就不细说了,可参考前面的文章.

总的来说,模块化设计的基础就是 面向抽象


模块化进阶一、使用单独的静态库工程提供子模块功能,然后再通过CocoaPods自动下载导入

  • 静态库提供的形式

    • 以 .a + .h 形式提供
    • 以 .framework 形式提供
      • 直接提供给别人一个单独的framework文件即可
      • 可以更好的打包资源文件
      • 还可以通过 CocoaPods 来实现像第三方框架那样集成
        • 参考 pod lib create
  • 一个完整的App最后是由n个单独屏蔽了内部实现的静态库合并起来的

    • 静态库A: 提供显示新闻列表
    • 静态库B: 提供商品列表
    • 静态库C: 提供支付
    • 静态库D: 等等…就是一个单独的业务功能模块
    • 再也会产生 .XcodeProj文件产生代码冲突了
    • 每一个静态库使用一个单独的podspec,作为一个pods源
  • 将所有的子功能模块做成私有的git库,然后使用CocoaPods导入

技术分享

  • CocoaPods中podfile文件中的一些配置属性含义
12345
platform: 可以指定平台的信息和deployment target的版本target: 可以根据不同的target来引入不同的podpod: 引入依赖库
12345678910111213141516171819
pod SSZipArchive-- 引入最新版本pod Objection, 0.9-- 引入特定的版本pod Objection, >0.9> -- 任何大于0.9的版本pod Objection, >=0.9> -- 任何大于等于0.9的版本pod Objection, <0.9> -- 任何小于0.9的版本pod Objection, <=0.9> -- 任何小于等于0.9的版本pod Objection, ~>0.9> -- 任何介于0.91.0的最新版本,不包含1.0pod AFNetworking, :path => ~/Documents/AFNetworking-- 使用本地路径引入pod AFNetworking, :git => https://github.com/gowalla/AFNetworking.git‘, :tag => ‘0.7.0‘  -- 使用git库引入pod JSONKit, :podspec => https://example.com/JSONKit.podspec‘  -- 使用外部的podspec来引入
  • 关于使用CocoaPods开发 static framework 的步骤可参看之前的文章.

模块化进阶二、使用单独的子App提供子模块功能

技术分享

  • 主App依赖n个子App完成对应的子功能模块
  • 核心是根据 URL Scheme 来完成App的跳转

    • 可参考开源类库 JLRoutes 封装的复杂性URL Scheme跳转
    • 未按照则跳转AppStore下载安装
  • 多个子App的好处与坏处

    • 每一个单独的子App,可以单独的测试、发布
    • 坏处那就是App太多了…

列举下JLRoutes的简单用法

  • App启动时注册URL Scheme的回调处理
12345678910111213141516171819
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  [JLRoutes addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {    // This block is called if the scheme is not ‘thing‘ or ‘stuff‘ (see below)    return YES;  }];    [[JLRoutes routesForScheme:@"thing"] addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {    // This block is called for thing://foo    return YES;  }];    [[JLRoutes routesForScheme:@"stuff"] addRoute:@"/foo" handler:^BOOL(NSDictionary *parameters) {    // This block is called for stuff://foo    return YES;  }];  return YES;}
  • 当前App被打开时的回调函数,使用JLRoutes处理当前(iso9+)
123
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options {  return [JLRoutes routeURL:url];}
  • 当前App被打开时的回调函数,使用JLRoutes处理当前(iso9之前)
123
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {    return [JLRoutes routeURL:url];}

更多的使用还是要项目用到的时候再去看了…

模块化开发iOSApp