V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
newkengsir
V2EX  ›  iOS

BeeHive 是用于 iOS 的 App 模块化编程的框架实现方案

  •  
  •   newkengsir · 2016-11-23 10:42:44 +08:00 · 3346 次点击
    这是一个创建于 2683 天前的主题,其中的信息可能已经有所发展或是发生改变。

    BeeHive

    http://www.code4app.com/forum.php?mod=viewthread&tid=11674&extra=page%3D1

    https://github.com/alibaba/BeeHive


    0. 概述

    BeeHive是用于iOSApp模块化编程的框架实现方案,吸收了Spring框架Service的理念来实现模块间的API耦合。

    0.1 基本架构

    0.2 实现特性

    • 插件化的模块开发运行框架
    • 模块具体实现与接口调用分离
    • 模块生命周期管理,扩展了应用的系统事件

    0.3 设计原则

    因为基于SpringService理念,虽然可以使模块间的具体实现与接口解耦,但无法避免对接口类的依赖关系。

    为什么不使用invoke以及动态链接库技术实现对接口实现的解耦,类似ApacheDSO的方式?

    主要是考虑学习成本难度以及动态调用实现无法在编译检查阶段检测接口参数变更等问题,动态技术需要更高的编程门槛要求。

    0.4 项目名来源

    BeeHive灵感来源于蜂窝。蜂窝是世界上高度模块化的工程结构,六边形的设计能带来无限扩张的可能。所以我们用了BeeHive来做为这个项目的命名。

    1 模块生命周期的事件

    BeeHive会给每个模块提供生命周期事件,用于与BeeHive宿主环境进行必要信息交互,感知模块生命周期的变化。

    事件分为三种类型:

    • 系统事件
    • 通用事件
    • 业务自定义事件

    1.1 系统事件

    系统事件通常是Application生命周期事件,例如DidBecomeActiveWillEnterBackground等。

    系统事件基本工作流如下:

    1.2 通用事件

    在系统事件的基础之上,扩展了应用的通用事件,例如modSetupmodInit等,可以用于编码实现各插件模块的设置与初始化。

    扩展的通用事件如下:

    1.3 业务自定义事件

    如果觉得系统事件、通用事件不足以满足需要,我们还将事件封装简化成BHAppdelgate,你可以通过继承 BHAppdelegate来扩展自己的事件。

    2. 模块注册

    模块注册的方式有静态注册与动态注册两种。

    2.1 静态注册

    通过在BeeHive.plist文件中注册符合BHModuleProtocol协议模块类:

    2.2 动态注册

    @implementation HomeModule
    
    BH_EXPORT_MODULE()  // 声明该类为模块入口
    
    @end
    

    在模块入口类实现中 使用BH_EXPORT_MODULE()宏声明该类为模块入口实现类。

    2.3 异步加载

    如果设置模块导出为BH_EXPORT_MODULE(YES),则会在启动之后第一屏内容展现之前异步执行模块的初始化,可以优化启动时时间消耗。

    3. 编程开发

    BHModuleProtocol为各个模块提供了每个模块可以Hook的函数,用于实现插件逻辑以及代码实现。

    3.1 设置环境变量

    通过context.env可以判断我们的应用环境状态来决定我们如何配置我们的应用。

    -(void)modSetup:(BHContext *)context
    {
        switch (context.env) {
            case BHEnvironmentDev:
            //....初始化开发环境
            break;
            case BHEnvironmentProd:
            //....初始化生产环境
            default:
            break;
        }
    }
    

    3.2 模块初始化

    如果模块有需要启动时初始化的逻辑,可以在modInit里编写,例如模块注册一个外部模块可以访问的Service接口

    -(void)modInit:(BHContext *)context
    {
        //注册模块的接口服务
        [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
    }
    
    

    3.3 处理系统事件

    系统的事件会被传递给每个模块,让每个模块自己决定编写业务处理逻辑,比如3D-Touch功能

    -(void)modQuickAction:(BHContext *)context
    {
        [self process:context.shortcutItem handler:context.scompletionHandler];
    }
    

    3.4 模间调用

    通过处理Event编写各个业务模块可以实现插件化编程,各业务模块之间没有任何依赖,coremodule之间通过event交互,实现了插件隔离。但有时候我们需要模块间的相互调用某些功能来协同完成功能。

    通常会有三种形式的接口访问形式:

    1. 基于接口的实现Service访问方式(Java spring框架实现)
    2. 基于函数调用约定实现的Export Method(PHPextensionReactNatve的扩展机制)
    3. 基于跨应用实现的URL Route模式(iPhone App之间的互访)

    我们目前实现了第一种方式,后续会逐步实现后两种方式。

    基于接口Service访问的优点是可以编译时检查发现接口的变更,从而及时修正接口问题。缺点是需要依赖接口定义的头文件,通过模块增加得越多,维护接口定义的也有一定工作量。

    3.4.1 定义接口

    以为HomeServiceProtocol为例。

    @protocol HomeServiceProtocol <NSObject, BHServiceProtocol>
    
    - (void)registerViewController:(UIViewController *)vc title:(NSString *)title iconName:(NSString *)iconName;
    
    @end
    

    3.4.2 注册Service

    有三种方式:

    声明式注册

    @implementation HomeService
    
    BH_EXPORT_SERVICE()
    
    @end
    

    API注册

    [[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];
    

    BHService.plist注册

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
        <dict>
            <key>HomeServiceProtocol</key>
            <string>BHViewController</string>
        </dict>
    </plist>
    
    

    3.4.3 调用Service

    #import "BHService.h"
    
    id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
    

    3.5 单例与多例

    对于有些场景下,我们访问每个声明Service的对象,希望对象能保留一些状态,那我们需要声明这个Service对象是一个单例对象。

    我们只需要在Service对象中实现事件函数

    声明

    -(BOOL) singleton
    {
        return YES;
    }
    
    

    通过createService获取的对象则为单例对象,如果实现上面函数返回的是NO,则createService返回的是多例。

    id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
    

    3.6 上下文环境 Context

    • 初始化设置应用的项目信息,并在各模块间共享整个应用程序的信息
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        [BHContext shareInstance].env = BHEnvironmentDev; //定义应用的运行开发环境
        [BHContext shareInstance].application = application;
        [BHContext shareInstance].launchOptions = launchOptions;
        [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/CustomModulePlist";//可选,默认为 BeeHive.bundle/BeeHive.plist
        [BHContext shareInstance].serviceConfigName =  @"BeeHive.bundle/CustomServicePlist";//可选,默认为 BeeHive.bundle/BHService.plist
        [[BeeHive shareInstance] setContext:[BHContext shareInstance]];
    
        [super application:application didFinishLaunchingWithOptions:launchOptions];
    
        id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
    
        if ([homeVc isKindOfClass:[UIViewController class]]) {
            UINavigationController *navCtrl = [[UINavigationController alloc] initWithRootViewController:(UIViewController*)homeVc];
    
            self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
            self.window.rootViewController = navCtrl;
            [self.window makeKeyAndVisible];
        }
    
        return YES;
    }
    

    更多细节可以参考 Example 用例。

    4. 集成方式

    cocoapods

    pod "BeeHive", '1.0.0'
    

    5. 作者

    6. 开源许可证

    BeeHive is available under the GPL license. See the LICENSE file for more info.

    11 条回复    2016-11-23 21:50:49 +08:00
    so898
        1
    so898  
       2016-11-23 10:54:59 +08:00
    G …… GPL ……

    看看算了……
    queuey
        2
    queuey  
       2016-11-23 11:06:33 +08:00
    GPL 阿里果然是中国三大巨头。领先 Facebook100 年
    kera0a
        3
    kera0a  
       2016-11-23 11:13:38 +08:00
    GPL 还怎么用啊。
    不过可以学习下
    eddiechen
        4
    eddiechen  
       2016-11-23 12:32:05 +08:00
    留个脚印,纯看下, iOS 一直没找到好用的框架
    fengyunSmlie
        5
    fengyunSmlie  
       2016-11-23 13:21:55 +08:00
    同留脚印 iOS 搭框架 一直没想到好的
    DingSoung
        6
    DingSoung  
       2016-11-23 14:04:01 +08:00
    MARK
    dorentus
        7
    dorentus  
       2016-11-23 14:58:34 +08:00 via iPhone
    GPL 玩个屁啊
    newkengsir
        8
    newkengsir  
    OP
       2016-11-23 15:53:02 +08:00
    newkengsir
        9
    newkengsir  
    OP
       2016-11-23 15:53:21 +08:00
    func didReceive(_ response: UNNotificationResponse,
    completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {

    if response.actionIdentifier == Identifiers.cancelAction {
    let request = response.notification.request

    let identifiers = [request.identifier]

    // Remove future notifications that have been scheduled
    UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)

    // Remove any notifications that have already been delivered so we're not cluttering up the user's notification center
    UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)

    // Visual feedback that notification has been cancelled
    speakerLabel.text = "🔇"
    speakerLabel.cancelShake()

    completion(.doNotDismiss)
    }
    else {
    completion(.dismiss)
    }
    }
    newkengsir
        10
    newkengsir  
    OP
       2016-11-23 15:53:34 +08:00
    ```
    func didReceive(_ response: UNNotificationResponse,
    completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {

    if response.actionIdentifier == Identifiers.cancelAction {
    let request = response.notification.request

    let identifiers = [request.identifier]

    // Remove future notifications that have been scheduled
    UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)

    // Remove any notifications that have already been delivered so we're not cluttering up the user's notification center
    UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)

    // Visual feedback that notification has been cancelled
    speakerLabel.text = "🔇"
    speakerLabel.cancelShake()

    completion(.doNotDismiss)
    }
    else {
    completion(.dismiss)
    }
    }
    ```
    expkzb
        11
    expkzb  
       2016-11-23 21:50:49 +08:00
    真没啥必要
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5373 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 08:01 · PVG 16:01 · LAX 01:01 · JFK 04:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.