美文网首页
BigApp2.0组件开发文档

BigApp2.0组件开发文档

作者: 大江哥哥 | 来源:发表于2019-06-28 10:02 被阅读0次

    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.podspecpod_spec_lintpush_specreplace_tag.gitignore文件拷贝到动态库工程根目录下
      [图片上传失败...(image-9570c4-1561687326420)]

    • 用文本编辑器分别打开pod_spec_lintpush_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;
      • 入参(NSString *)URL 是由需要调用组件的类名,调用组件方法的入参注释拼接而成,示例如图:

        image
      • 入参(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工程中组件入口类ACComponentDemoHome类中,在路由分发到组件类系统ApplicationDelegate分发事件中初始化界面tabbar,调用ACComponentDemoView提供的路由方法如图:

      image
    • 在ACComponentDemo2工程中组件入口类ACComponentDemoView类中,提供tabbar子视图的方法如图:

      image

    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文件。
    image

    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 组件开发,务必遵循以下几点

    • 组件库必须使用动态库
    image
    • 组件库名和组件类名需要保持一致
    image
    • 组件类必须加上public字段,组件暴露的方法必须加上@objc字段
    image

    10. 组件发布(pod模式)

    进行本章节操作时,我们已假定你已按照第3和第4章节完成组件的创建

    完成组件发布后,其他开发者才能通过pod集成该组件

    • 确认组件工程本地仓库中需要提交的代码已提交完成,其中的podspec文件已是最新
    • 确认pod_spec_lintpush_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

    相关文章

      网友评论

          本文标题:BigApp2.0组件开发文档

          本文链接:https://www.haomeiwen.com/subject/afajcctx.html