模块化开发iOSApp

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模块化开发iOSApp相关的知识,希望对你有一定的参考价值。

一个iOS菜菜的白话文记录

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

模块化开发iOSApp一

FEB 26TH, 2016 11:45 AM

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

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

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

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

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

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

  • 假设在MainViewController要push到一个显示新闻列表的NewsListViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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的思路

抽象接口 + 容器注入

  • 抽象接口定义:显示新闻列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
//  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
  • 接口实现类,具体怎么样显示新闻列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
//  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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
//  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依赖一个容器获取接口实现类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
//  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
  • 中间容器,只是随便写的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
//  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文件中的一些配置属性含义
1
2
3
4
5
platform: 可以指定平台的信息和deployment target的版本

target: 可以根据不同的target来引入不同的pod

pod: 引入依赖库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.0

pod 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的回调处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (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+)
1
2
3
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options {
  return [JLRoutes routeURL:url];
}
  • 当前App被打开时的回调函数,使用JLRoutes处理当前(iso9之前)
1
2
3
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [JLRoutes routeURL:url];
}

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

以上是关于模块化开发iOSApp的主要内容,如果未能解决你的问题,请参考以下文章

基于LinkMap分析iOSAPP各模块体积

Xcode开发中的6个小技巧

Android开发——UI_片段

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

文顶顶iOS开发博客链接整理及部分项目源代码下载

开源一个完整的iOSApp《丁丁美图》供初学者学习