BigApp2.0组件开发文档
1. 简介
BigApp2.0的开发使用了组件化结构设计。组件之间相互独立,不存在依赖关系,通过框架层的路由组件进行数据通信降低耦合度。组件化使得不同团队并行开发各业务,极大提高迭代效率。本文介绍了组件的开发、调试、测试、发布等一系列流程。
参照本文档进行开发时,我们已假定你有一定的使用Swift或OC语言进行iOS开发的经验和CocoaPods的使用经验。
iOS组件入口类必须继承ACComponentBase类。iOS组件入口类类名需要配置到component.xml中。
2. 开发环境
- OS X 10.11.5+
- Xcode 8.0+
- iOS组件开发基础包
3. 准备组件开发
3.1 创建动态库工程
本文以创建和开发ACComponentItemMessage组件为例,参照本文开发时,创建的组件名称请根据实际情况命名。
-
打开Xcode,在菜单栏中选择 File - New - Project...
-
选择 iOS - Framework & Library - Cocoa Touch Framework
-
填入组件基本信息, Product Name填
ACComponentItemMessage
,点击Next -
选择动态库工程的保存地址,点击Create,建立一个动态库工程(建议把所有组件和调试用的主工程放在同一个目录下,方便后面调试)
[图片上传失败...(image-e54760-1561687326420)] -
编辑target工程Build Settings,将
Mach-o Type
修改为Dynamic Library
(Swift组件只支持动态库,所以选择Dynamic Library
,纯OC组件可以改为Static Library
,也就是静态库)
[图片上传失败...(image-3d882d-1561687326420)] -
编辑target工程Build Settings,将
Enable Bitcode
修改为No
[图片上传失败...(image-a72ab7-1561687326420)] -
打开iOS组件开发基础包,把
ACComponentItemDemo.podspec
、pod_spec_lint
、push_spec
、replace_tag
、.gitignore
文件拷贝到动态库工程根目录下
[图片上传失败...(image-9570c4-1561687326420)] -
用文本编辑器分别打开
pod_spec_lint
和push_spec
脚本,把里面的ACComponentItemDemo
字段替换为自己的组件名 -
修改
ACComponentItemDemo.podspec
文件,如下图
[图片上传失败...(image-24e8cf-1561687326420)]
3.2 创建索引
- 把iOS组件开发基础包中的ACComponentItemDemo文件夹拷贝到linewell-specs索引库的本地仓库中,文件夹名
ACComponentItemDemo
修改为自己创建的组件名,文件夹路径下的ACComponentItemDemo.podspec
文件替换为自己组件工程目录下的podspec文件
3.3 组件提交
- 联系GitLab管理员创建完该组件的远程仓库后,把组件工程提交到远程仓库中
- 把linewell-specs索引库中该组件部分提交到远程仓库中
3.4 组件调试
- 把主工程BigApp从远程仓库克隆至创建的组件同一目录下
[图片上传失败...(image-965461-1561687326420)] - 修改主工程的Podfile文件,在Podfile文件添加本组件的本地引用,如图
[图片上传失败...(image-46c5a6-1561687326420)] - 双击执行主工程目录下的bootstrap脚本,完成后主工程会自动打开
- 在主工程的
component.xml
文件中添加该组件,这时就可以在Development Pods
中进行组件的开发了,如图
[图片上传失败...(image-1b2c2c-1561687326420)]
4. 组件开发
4.1 编写组件入口类
- 统一规定,组件入口类与组件名相同
- 在ACComponentItemMessage工程中创建组件入口类ACComponentItemMessage,ACComponentItemMessage中引入
<ACRouterKit/ACRouterKit.h>
,并使此类继承ACComponentBase
。 - 在
ACComponentItemMessage
类中实现生命周期方法:
//objc
- (instancetype)initWithApp:(id<ACComponentBaseProtocol>)app {
self = [super initWithApp:app];
if (self) {
NSLog(@"BigApp-->ACComponentItemMessage-->initWithApp");
}
return self;
}
//swift
override public init!() {
super.init()
}
override public init!(app: ACComponentBaseProtocol!) {
super.init(app: app)
LogPrint("BigApp-->ACComponentItemMessage-->initWithApp")
}
组件中类的命名规则
- 组件的入口类必须命名为
ACComponent
开头的类名。 - 组件中其他的类无命名限制,但建议增加独特的前缀,以避免和引擎以及其他组件中的类产生类名冲突,导致打包失败。
ACComponentBase
简介
-
ACComponentBase
是组件入口的基类,所有的组件入口类都必须继承自此类。 -
ACComponentBase
拥有1个实例变量和1个实例方法 - 实例变量
appContext
是一个弱引用,指向ACComponentBaseProtocol
协议,该协议包含系统的UIApplication *applicationContext
UIWindow *mainWindow
实例对象; - 实例方法
initWithApp:
是默认的初始化方法。- 程序启动时会调用组件初始化方法
-
组件入口子类可以覆写此方法进行自定义初始化设置,但必须调用父类的此方法。
image
ACRouter
路由简介
-
ACRouter
是由ACRouterKit.framework
提供的路由工具,是组件之间的通讯的桥梁,实现了组件之间不需要引用就可以交互的功能。 -
ACRouter
拥有一个实例方法和一个类方法。 - 类方法
+ (instancetype) route;
为ACRouter单例方法,创建路由必须通过该方法。 - 实例方法
- (id)openURL:(NSString *)URL toHandler:(ACRouterHandler)handler;
组件之间通过该方法相互通讯。
4.2 编写组件方法并调用
本小节示范了如何让一个ACComponentDemo2组件任意类去调用ACComponentDemo1组件入口类暴露的一个方法showAlert:
并回调结果
- 在ACComponentDemo1类中实现一个方法
showAlert:
:
//objective-c
-(void)showAlert:(NSDictionary *)params {
ACRouterHandler handle = [params objectForKey:@"routehandler"];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:params[@"title"] message:params[@"message"] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ation1 = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
if (handle) {
handle(1,@"你点击了确定",@{@"info":@"确定"});
}
}];
UIAlertAction *ation2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
if (handle) {
handle(0,@"你点击了确定",@{@"info":@"取消"});
}
}];
[alert addAction:ation1];
[alert addAction:ation2];
[self.appContext.mainWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
- 在ACComponentSwift组件中调用ACComponentMyView组件暴露的
provideAnButton
方法并回调结果 :
//swift
let routehandler = {(code:Int32, msg:Optional<String>, data:Optional<Dictionary<AnyHashable, Any>>) -> ()in
let alert = UIAlertView.init(title: "提示", message: "swift已点击", delegate: self, cancelButtonTitle: "确定");
alert.show();
}
var params : [String : AnyObject] = [String : AnyObject]()
params["x"] = "50" as AnyObject
params["y"] = "200" as AnyObject
params["width"] = "200" as AnyObject
params["height"] = "80" as AnyObject
params["content"] = "我是Swift按钮" as AnyObject
let myBtn = ACRouter.route().performTarget("ACComponentMyView", action:"provideAnButton", params:params,toHandler:routehandler);
- 在ACComponentSwift组件中暴露一个方法
blockTest
并执行回调
//swift
@objc func blockTest(_ params:NSDictionary) {
let handler = params.value(forKey: "routehandler");
ACRouter.route().realizeCode(1, msg: "swift11", data: nil, block:handler);
};
组件入口类中实现供其他组件调用的方法的注意事项
1 方法只有一个入参 `(NSDictionary *)params`
2 暴露该组件入口类头文件中需要有必要的入参注释,如下:
/**
入参params字典中需包含
1 title 弹窗名称
2 message 弹窗信息
*/
-(void)showAlert:(NSDictionary *)params;
3 ACRouterHandler 为默认回调,在方法实现中需要实现回调可用`[params objectForKey:@"routehandler"]`取得,组件中回调均需要遵守回调格式` typedef void(^ACRouterHandler)(int code, NSString *msg ,NSDictionary *data);`
* int code 区别回调类型
* NSString *msg 回调信息
* NSDictionary *data 回调数据
组件方法调用基本规则
- 组件之间的通讯必须通过调用
ACRouterKit
中的路由方法
-(id)openURL:(NSString *)URL toHandler:(ACRouterHandler)handler;
-
入参
image(NSString *)URL
是由需要调用组件的类名,调用组件方法的入参注释拼接而成,示例如图:
-
入参
(ACRouterHandler)handler
默认回调参数
-
组件方法调用示例
- 在ACComponentDemo2工程中
ViewController1
类通过路由组件ACRouter
调用ACComponentDemo1工程中组件入口类ACComponentDemoHome
暴露出的方法-(void)showAlert:(NSDictionary *)params;
,如下:
//objective-c
[[ACRouter route] openURL:@"ACComponentDemoHome://showAlert?title=提示&message=来自Demo2" toHandler:^(int code, NSString *msg, NSDictionary *data) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}];
4.3 通过组件构建简单应用
-
本小节示范了如何通过组件构建具有tabbar的简单应用
-
在ACComponentDemo1工程中组件入口类
imageACComponentDemoHome
类中,在路由分发到组件类系统ApplicationDelegate分发事件中初始化界面tabbar,调用ACComponentDemoView
提供的路由方法如图:
-
在ACComponentDemo2工程中组件入口类
imageACComponentDemoView
类中,提供tabbar子视图的方法如图:
5. 生成组件包
本章讲述组件包的生成,目前已经可以通过主工程中的frameworks_build
脚本批量生成,无需按下面的步骤逐步操作
5.1 编译组件静态库.framework文件
- 关闭ACMobiNativeMain调试工程,然后打开ACComponentDemo1.xcodeproj。
- 选择
ACComponentDemo1
-Generic iOS Device
。 - 点击Product-Build,生成组件的.framework文件
ACComponentDemo1.framework
。 - 生成目录为ACComponentnDemo1/build/ACComponentDemo1。
- 新建component.xml和info.xml文件。
5.2 编辑component.xml
- component.xml记录了组件的入口类信息。其中plugin name是组件的入口类
ACComponentDemoHome
名称。 - 最终完成的component.xml示例如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
<plugin name="ACComponentDemoHome"></plugin>
</acplugins>
5.3 编辑info.xml
- info.xml主要记录了组件的版本信息
- 由于组件也是插件的一种形式,因此info.xml格式与插件的info.xml格式基本一致
- 示例模板如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
<plugin
acName="ACComponentDemoHome" version="1.0.1" build="1" desc='组件示例' type="compoment">
</plugin>
</acplugins>
其中acName
替换成组件入口类对象名。x替换成当前组件的版本号(非负整数)
- 然后向plugin节点中加入各个版本的简介,这些简介以倒序加入,由一个
<info>
节点和多个(可以为0个)<build>
节点构成。-
<info>
节点记录了当前版本的简介 -
<build>
节点记录了历史版本的简介 -
<desc>
节点记录了组件的简介 - `<type="compoment">节点说明该库为组件
- 当组件版本更新时,应该将当前的
<info>
节点改为<build>
节点,同时在其之前添加新的<info>
节点
-
- 最终完成的info.xml范例如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
<plugin
acName="ACComponentDemoHome" version="1.0.1" build="1" desc='组件示例' type="compoment">
<info>1:版本更新记录</info>
<build>0:iOS组件范例</build>
</plugin>
</acplugins>
6. 其他开发说明
6.1 引入第三方库和bundle资源
- 动态库形式的第三方库只需要在组件的podspec文件中添加dependency即可
- 引入静态库第三库,需要先把真实文件放入组件工程路径下,再在组件的podspec文件中添加对应的subspec
[图片上传失败...(image-4dc42b-1561687326420)] - 引入bundle,需要先把真实文件放入组件工程路径下,再在组件的podspec文件中添加resources
[图片上传失败...(image-9ea3b5-1561687326420)] - bundle的swift调用方式
[图片上传失败...(image-8b60ac-1561687326420)] - 注意:引用的第三方库和bundle放入本地,并在podspec文件中进行相应修改后,需要进行组件发布(见第10章节)和重新执行主工程中的bootstrap脚本,才能在主工程中进行调用的调试(引用的第三方动态库不需要放入本地)
-
注意:静态库framework中如果没有modulemap,则swift组件无法直接调用
[图片上传失败...(image-5adec-1561687326420)]
6.2 组件如何获取系统事件
6.2.1 ApplicationDelegate事件
路由会将大部分ApplicationDelegate事件分发到每个组件入口类,组件入口类用相应的类方法接收即可。目前组件入口类可供接收的类方法有:
+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
+ (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+ (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
+ (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
+ (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
+ (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
+ (void)applicationWillResignActive:(UIApplication *)application;
+ (void)applicationDidBecomeActive:(UIApplication *)application;
+ (void)applicationDidEnterBackground:(UIApplication *)application;
+ (void)applicationWillEnterForeground:(UIApplication *)application;
+ (void)applicationWillTerminate:(UIApplication *)application;
+ (void)applicationDidReceiveMemoryWarning:(UIApplication *)application;
+ (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler;
+ (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler;
+ (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler;
//UNUserNotificationCenterDelegate方法(iOS 10+)
//注意此方法的completionHandler参数应为`UNNotificationPresentationOptions`
+ (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSUInteger))completionHandler;
+ (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;
示例:
//objective-c
//ACComponentDemoHome.m中
static NSDictionary *AppLaunchOptions;
+ (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSLog(@"app launched");
//存储launchOptions
AppLaunchOptions = launchOptions;
return YES;
}
//swift
//ACComponentSwift.swift中
@objc class func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
NSLog(@"app launched");
return true
}
6.3.2 设置App启动页面
自定义组件中可以使用ApplicationDelegate系统事件设置app的入口界面,
image
*设置 [ACComponentMgr sharedComponentMgr].appDelegate.mainWindow.rootViewController 即可
7. 自定义组件View
自定义组件ACComponentMyView提供了自定义View的创建方法,和对外暴露的设置属性路由
7.1 自定义组件View开发
自定义组件view首先遵从组件的基本开发模式
7.2 获取自定义view实例对象
可以通过路由的方法直接获取自定义view实例对象
路由方法示例:
- (id)provideAnButton:(NSDictionary *)params {
CGFloat x = [params[@"x"] floatValue];
CGFloat y = [params[@"y"] floatValue];
CGFloat width = [params[@"width"] floatValue];
CGFloat height = [params[@"height"] floatValue];
NSString *content = params[@"content"];
ACRouterHandler handler = [params objectForKey:@"routehandler"];
ACMyButton *myBtn = [[ACMyButton alloc] initWithFrame:CGRectMake(x, y, width, height)];
[myBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[myBtn setTitle:content forState:UIControlStateNormal];
myBtn.handler = handler;
return myBtn;
}
路由调用示例:
//objective-c
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:1];
params[@"x"] = @"50";
params[@"y"] = @"200";
params[@"width"] = @"200";
params[@"height"] = @"80";
params[@"content"] = @"我是按钮";
params[@"routehandler"] = ^(int code, NSString *msg ,NSDictionary *data){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:msg delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
alert.delegate = self;
[alert show];
};
UIView *myBtn = [[ACRouter route] performTarget:@"ACComponentMyView" action:@"provideAnButton" params:params];
myBtn.tag = kBTNTAG;
[self.view addSubview:myBtn];
//swift
let routehandler = {(code:Int32, msg:Optional<String>, data:Optional<Dictionary<AnyHashable, Any>>) -> ()in
let alert = UIAlertView.init(title: "提示", message: "swift已点击", delegate: self, cancelButtonTitle: "确定");
alert.show();
}
var params : [String : AnyObject] = [String : AnyObject]()
params["x"] = "50" as AnyObject
params["y"] = "200" as AnyObject
params["width"] = "200" as AnyObject
params["height"] = "80" as AnyObject
params["content"] = "我是Swift按钮" as AnyObject
let myBtn = ACRouter.route().performTarget("ACComponentMyView", action:"provideAnButton", params:params,toHandler:routehandler);
7.3 通过路由方法设置自定义view的属性
可以通过路由的方法直接设置自定义view的属性
路由方法示例:
-(void)setMyBtn:(NSDictionary *)params {
ACMyButton *myBtn = params[@"myBtn"];
NSString *content = params[@"content"];
[myBtn setTitle:content forState:UIControlStateNormal];
}
路由调用示例:
UIView *myBtn = [self.view viewWithTag:kBTNTAG];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithCapacity:1];
params[@"myBtn"] = myBtn;
params[@"content"] = @"已点击";
[[ACRouter route] performTarget:@"ACComponentMyView" action:@"setMyBtn" params:params];
8. 消息订阅和广播
实现消息的订阅和广播需要遵循一下步骤
8.1 注册消息
在组件的compoent.xml需要声明可被订阅的消息,如下
<?xml version="1.0" encoding="utf-8" ?>
<acplugins>
<plugin name="ACComponentMyView">
<method name="ACComponentMyViewSignal1"></method>
<method name="ACComponentMyViewSignal2"></method>
<method name="ACComponentMyViewSignal3"></method>
<method name="ACComponentMyViewSignal4"></method>
</plugin>
</acplugins>
8.2 订阅消息
通过路由暴露出的方法订阅消息,
/**
监听模块信号
*/
- (void)subscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject withAction:(SEL)listenerAction;
其中moduleName为需要订阅对象的名称;
signal为订阅消息的名称;
Listener为订阅者;
action为订阅触发的方法;
示例如下
- (void)subscription {
[[ACRouter route] subscriptionModule:@"ACComponentMyView" Signal:@"ACComponentMyViewSignal1" Listener:self withAction:@selector(alertMsg:)];
}
- (void)alertMsg:(id)data {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:data[@"msg"] delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}
8.3 发布消息
组件通过路由方法发布消息
/**
发布模块信号
*/
- (void)publishModule:(NSString *)publishName withSignal:(NSString *)signal params:(id)data;
publishName为发布消息对象的名称;
signal为消息的名称;
params为传参;
示例如下:
[[ACRouter route] publishModule:@"ACComponentMyView" withSignal:@"ACComponentMyViewSignal1" params:@{@"msg":@"btn已修改"}];
8.4其他
移除消息订阅
/**
移除信号
*/
- (void)removeSubscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject;
moduleName为订阅对象的名称;
signal为消息的名称;
listener为订阅者;
示例如下:
[[ACRouter route] removeSubscriptionModule:@"ACComponentMyView" Signal:@"ACComponentMyViewSignal1" Listener:self];
9. swift 组件开发需要注意
如果使用siwft 组件开发,务必遵循以下几点
- 组件库必须使用动态库
- 组件库名和组件类名需要保持一致
- 组件类必须加上
public
字段,组件暴露的方法必须加上@objc
字段
10. 组件发布(pod模式)
进行本章节操作时,我们已假定你已按照第3和第4章节完成组件的创建
完成组件发布后,其他开发者才能通过pod集成该组件
- 确认组件工程本地仓库中需要提交的代码已提交完成,其中的podspec文件已是最新
- 确认
pod_spec_lint
和push_spec
脚本中的组件名是自己的组件名 - 双击执行
pod_spec_lint
脚本(验证podspec文件中的代码是否错误),确认完成无报错 - 双击执行
push_spec
脚本(将podspec文件推送到索引库) - 双击执行
replace_tag
脚本(推送新的2.0.0标签到远程仓库) - 以上操作完成没有问题后,开发者在主工程的Podfile文件中添加
pod 'ACComponentItemDemo', '2.0.0'
(ACComponentItemDemo替换为自己的组件),执行bootstrap脚本即可把组件集成到主工程中
注意:若执行bootstrap脚本时下载下来的组件代码仍不是组件最新代码,可以用文本编辑器打开bootstrap脚本文件,在pod install
的命令之前写入pod cache clean ACComponentItemDemo
代码,重新执行bootstrap脚本即可清除对应缓存并下载最新代码
11. 更新历史
最新版本:1.0.0
最近更新时间:20190628
网友评论