-
Notifications
You must be signed in to change notification settings - Fork 61
4. Component (组件式开发)
使用组件开发已经是软件开发的热门方式,就前端和移动端更以URL路由组件方式更为常见,为了追赶这股技术潮流,乐高在2.0版本开始就开始加入这个概念。但是在乐高中的URL路由组件方式和别的框架有几点不同之处:
- URL路由方式和网址相似(支持网址参数)
- 以组件名的注册方式
- 组件打开方来完成下一个组件切换方式
这样设计是为了完美整合进乐高的模块运行体系,因为乐高1.x版本的模块高度耦合在一在,使用这种简化版的URL路由方式,使模块各自独立,提高了可维护性,注意的是这种设计只为了处理组件(VIPER模块组件、MVC模块组件、MVVM模块组件等)切换,不针对URL路由作其它业务处理。简单来说就一句话:功能模块化,界面组件化。
引入组件开发方式,使VIPER模块或其它设计模式模块统一对待,以组件通用API实现对VIPER模块与其它设计模式模块的双向桥接,分为以下功能:
- 统一的URL路由跳转方法
- 强大的轻量级组件事件消息通信
- 内存安全的通知中心注册与接收方式
- 组件导航路径跟踪
要使一个设计模式模块要成为组件,必需使用导出宏来指定,使框架来识别它们具有这些功能:事件处理机制、URL路由跳转、组件参数传递、统一的组件生命周期等。那么怎样把它们导出为一个组件?
VIPER模块各层通过继承框架提供的父类层后,会自动转为一个组件。具体点来说是VIPER模块的事件层 XFPresenter
层会充当一个组件类型,因为它实现了 XFModuleComponentRunnable
接口,使当前模块成为可运行组件。
- MVC模块的继承方式
#import "XFLegoVIPER.h"
// 继承可运行组件控制器XFComponentViewController
@interface BDJPublishViewController : XFComponentViewController
@end
- MVC模块的分类方式
#import "XFLegoVIPER.h"
// 如要当前控制器不是继承的UIViewController类,可实现XFControllerComponentRunnable接口使控制器成为可运行组件
@interface BDJPostPictureBrowseViewController : UIViewController <XFControllerRunnable>
/* ---------------- 可选实现 ---------------- */
// 上一个URL组件传递过来的URL参数
@property (nonatomic, copy) NSDictionary *URLParams;
// 上一个URL组件传递过来的自定义数据对象
@property (nonatomic, copy) id componentData;
// 预设要传递给其它组件的意图数据
@property (nonatomic, copy) id intentData;
@end
@implementation BDJPostPictureBrowseViewController
// 把控制器导出为组件
XF_EXPORT_COMPONENT
@end
- 其它设计模式模块,如MVVM模块的导出方式,具体代码在
Extension/MVVM
下:
#import "XFLegoVIPER.h"
@interface LEViewModel : NSObject <LEDataDriverProtocol, XFControllerRunnable>
@property (nonatomic, weak, readonly) id<LEViewProtocol> view;
@property (nonatomic, strong, readonly) __kindof XFUIBus *uiBus;
@property (nonatomic, strong, readonly) __kindof XFEventBus *eventBus;
@property (nonatomic, weak, readonly) __kindof id<XFComponentRoutable> fromComponentRoutable;
@property (nonatomic, weak, readonly) __kindof id<XFComponentRoutable> nextComponentRoutable;
@property (nonatomic, copy) NSDictionary *URLParams;
@property (nonatomic, copy) id componentData;
@property (nonatomic, copy) id intentData;
@end
@implementation LEViewModel
// 导出为可运行组件
XF_EXPORT_COMPONENT
@end
任何一个可运行组件,都有获取焦点方法和失去焦点方法可以覆盖实现:
/**
* 组件将获得焦点
*/
- (void)componentWillBecomeFocus;
/**
* 组件将失去焦点
*/
- (void)componentWillResignFocus;
这种方式是:URL + 组件名
, 即一个组件名对应一个访问的URL。
// 注意组件名首字母要大写
[XFURLRoute register:@"bdj://indexTab" forComponent:@"IndexTab"];
[XFURLRoute register:@"bdj://indexTab/publish" forComponent:@"Publish"];
由于URL路径的最后一个路径就是组件名,所有可以使用快速初始化方式:
[XFURLRoute register:@"bdj://indexTab"];
[XFURLRoute register:@"bdj://indexTab/publish"];
更快的初始化URL列表:
// 使用自定义类封装起来
@implementation BDJAppURLRegister
+ (void)urlRegister
{
[XFURLRoute initURLGroup:@[
@"bdj://indexTab", // Tab主UI框架页
@"bdj://indexTab/publish", // 发布作品
@"bdj://friendTrends/friendsRecomment", // 推荐朋友
@"bdj://userCenter/signIn", // 登录
@"bdj://essence/recommendTag", // 推荐标签
@"bdj://essence/post/postPictureBrowse", // 浏览大图
@"bdj://essence/post/postComment", // 帖子评论
]];
}
@end
对于乐高框架来说,各模块的组件式跳转API宏都是一致的,包括导航的跳转,都由组件UI总线统一处理,只是不同的编写位置依赖于不同的设计模式模块,如:VIPER模块写在Routing
里,而MVC模块写在ViewController
里。
VIPER模块的组件入口是Presenter
,但视图跳转在Routing
:
@implementation BDJPostRouting
// 组装模块
XF_AutoAssemblyModule_Fast
- (void)transition2PostComment
{
// Push方式
XF_PUSH_URLComponent_Fast(@"bdj://essence/post/postComment")
}
- (void)transition2PostPictureBrowse
{
// Present方式
XF_Present_URLComponent_Fast(@"bdj://essence/post/postPictureBrowse")
}
- (void)transition2Publish
{
// 自定义跳转 (Publish组件为MVC模块组件)
[self.uiBus openURL:@"bdj://indexTab/publish" withTransitionBlock:^(__kindof UIViewController *thisInterface, __kindof UIViewController *nextInterface, TransitionCompletionBlock completionBlock) {
// 使用不带动画的方式
[thisInterface presentViewController:nextInterface animated:NO completion:completionBlock];
} customCode:nil];
}
@end
在iOS里MVC设计模式中,控制器和视图合并在一个ViewController类中,所以跳转也在ViewController:
#import "XFComponentViewController.h" // 导入头文件
// 继承可运行组件控制器XFComponentViewController
@interface BDJPublishViewController : XFComponentViewController
@end
@implementation BDJPublishViewController
- (void)doFunAction:(UIButton *)target
{
// 先显示退出动画
[self cancelWithCompletionBlock:^{
switch (target.tag) {
case BDJPublishTypeWords:
self.intentData = @"我是意图数据";
// PublishContent是VIPER模块组件
XF_Present_URLComponent_Fast(@"bdj://indexTab/publish/publishContent")
break;
default:
break;
}
[self dismissViewControllerAnimated:NO completion:nil];
}];
}
@end
当显示的下一个组件需要导航里,可以使用URL行为参数nav
,这个是作为框架的URL参数保留关键字,不会传递到下一个组件(什么是组件URL参数传递?),对于显示根组件、添加子组件、Present下一个组件都是有效的,且使用方法一样。它的值取决于你要什么类型的导航,如UI
代表UINavigationController
,如果有自定义的导航BDJNavigationController
,给它赋值BDJ
就可以了。
// 显示根组件
XF_ShowURLComponent2Window_(@"xf://search?nav=UI")
// 添加子组件
- (void)setUpChildActivitys {
[self addChildActivity:XF_SubUInterface_URL(@"bdj://indexTab/essence?nav=BDJ") title:@"精华" image:R_Image_TabBarEssence selImage:R_Image_TabBarEssenceSel];
[self addChildActivity:XF_SubUInterface_URL(@"bdj://indexTab/new?nav=BDJ") title:@"新帖" image:R_Image_TabBarNew selImage:R_Image_TabBarNewSel];
[self addChildActivity:XF_SubUInterface_URL(@"bdj://indexTab/friendTrends?nav=BDJ") title:@"关注" image:R_Image_TabBarFriendTrend selImage:R_Image_TabBarFriendTrendSel];
[self addChildActivity:XF_SubUInterface_URL(@"bdj://indexTab/me?nav=BDJ") title:@"我" image:R_Image_TabBarMe selImage:R_Image_TabBarMeSel];
}
// Present下一个组件
- (void)doFunAction:(UIButton *)target
{
// 先显示退出动画
[self cancelWithCompletionBlock:^{
switch (target.tag) {
case BDJPublishTypeWords:
// 使用nav行为参数标识装配自定义导航
XF_Present_URLComponent_Fast(@"bdj://indexTab/publish/publishContent?nav=BDJ")
break;
case BDJPublishTypePicture:
XF_Present_URLComponent_Fast(@"bdj://indexTab/publish/publishImg?nav=BDJ")
break;
default:
break;
}
[self dismissViewControllerAnimated:NO completion:nil];
}];
}
注意: 例子中的代码来自一个通过乐高框架编写的完整项目:BDJProjectExample