Skip to content

4. Component (组件式开发)

Yizzuide edited this page Feb 14, 2017 · 9 revisions

  使用组件开发已经是软件开发的热门方式,就前端和移动端更以URL路由组件方式更为常见,为了追赶这股技术潮流,乐高在2.0版本开始就开始加入这个概念。但是在乐高中的URL路由组件方式和别的框架有几点不同之处:

  • URL路由方式和网址相似(支持网址参数)
  • 以组件名的注册方式
  • 组件打开方来完成下一个组件切换方式

  这样设计是为了完美整合进乐高的模块运行体系,因为乐高1.x版本的模块高度耦合在一在,使用这种简化版的URL路由方式,使模块各自独立,提高了可维护性,注意的是这种设计只为了处理组件(VIPER模块组件、MVC模块组件、MVVM模块组件等)切换,不针对URL路由作其它业务处理。简单来说就一句话:功能模块化,界面组件化。

  引入组件开发方式,使VIPER模块或其它设计模式模块统一对待,以组件通用API实现对VIPER模块与其它设计模式模块的双向桥接,分为以下功能:

  • 统一的URL路由跳转方法
  • 强大的轻量级组件事件消息通信
  • 内存安全的通知中心注册与接收方式
  • 组件导航路径跟踪

导出组件

要使一个设计模式模块要成为组件,必需使用导出宏来指定,使框架来识别它们具有这些功能:事件处理机制、URL路由跳转、组件参数传递、统一的组件生命周期等。那么怎样把它们导出为一个组件?

导出VIPER模块组件

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模块组件跳转

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

MVC模块组件跳转

在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];
    }];
}