# AC-Component-Demo-iOS **Repository Path**: ACCCCloud/AC-Component-Demo-iOS ## Basic Information - **Project Name**: AC-Component-Demo-iOS - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2018-07-25 - **Last Updated**: 2023-11-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # AC-Component-Demo-iOS ## 1. 简介 ACMobi跨平台移动开发框架内部采用组件化结构设计。组件之间相互独立,不存在依赖关系,通过框架层的路由组件进行数据通信降低耦合度。组件化使得不同团队并行开发各业务,极大提高迭代效率。本文介绍了组件的开发、调试、测试、发布等一系列流程。 iOS组件入口类必须继承ACComponentBase类。iOS组件入口类类名需要配置到component.xml中。 ## 2. 开发环境 * OS X 10.11.5+ * Xcode 8.0+ * iOS组件开发基础包 ## 3. 组件开发 ### 3.1 创建静态库工程 * 打开Xcode,在菜单栏中选择 File - New - Project... * 选择 iOS - Framework & Library - Cocoa Touch Framework * 填入组件基本信息, Product Name填`ACComponentDemo1`,点击Next * 选择静态库工程的保存地址,点击Create,建立一个静态库工程 * 编辑target工程Build Settings,将`Per-configuration Build Products Path` 修改为`$SRCROOT/build/ACComponentDemo1 `(此设置使得工程编译得到的.framework文件会生成在工程目录下的`build/ACComponentDemo1 `文件夹中,方便后续的组件打包) ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/01.png) * 编辑target工程Build Settings,将`Mach-o Type` 修改为`Static Library` ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/02.png) * 编辑target工程Build Settings,将`Enable Bitcode` 修改为`No` ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/21.png) * 按上述步骤创建ACComponentDemo2静态库 ### 3.2集成调试工程 * 打开组件开发基础包中的ACMobiNativeMainDebug.xcodeproj,关闭ACComponentDemo1和ACComponentDemo2工程。 * 将ACComponentDemo1.xcodeproj 和ACComponentDemo2.xcodeproj分别引入到ACMobiNativeMainDebug.xcodeproj(直接拖拽即可)。 * 建立工程依赖,编辑主工程ACMobiNativeMainDebug这个target的Build Phases: * Link Binary With Libraries中,添加`ACComponentDemo1.framework`和`ACComponentDemo2.framework` ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/03.png) ### 3.3 组件调试工程简介 如下图,红框标注部分都是在组件开发调试中可能会用到的部分。 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/04.png) ## 4. 组件开发 ### 4.1 编写组件入口类 * 本小结介绍如何在ACComponentDemo1.xcodeproj工程中,编写组件入口类`ACComponentDemoHome`;在ACComponentDemo2.xcodeproj工程中,编写组件入口类`ACComponentDemoView`; * 在基础包ACMobiNativeMainDebug/ACEngine目录下找到`ACRouterKit.framework`, 分别拷贝到组件ACComponentDemo1目录下和组件ACComponentDemo2目录下,并引入到ACComponentDemo1.xcodeproj工程和ACComponentDemo2.xcodeproj工程中。 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/05.png) * 在ACComponentDemo1.xcodeproj工程中创建组件入口类 ACComponentHome中引入`` ,并使此类继承`ACComponentBase`。在ACComponentDemo2.xcodeproj工程中创建组件入口类 ACComponentView中引入`` ,并使此类继承`ACComponentBase`。 * 在`ACComponentHome`和`ACComponentView`类中实现生命周期方法: ```objc - (instancetype)initWithApp:(id)app { self = [super initWithApp:app]; if (self) { NSLog(@"ACComponentDemo initWithApp"); } return self; } ``` #### 组件中类的命名规则 * 组件的入口类**必须**命名为`ACComponent`开头的类名。 * 组件中其他的类**无命名限制,但建议增加独特的前缀**,以避免和引擎以及其他组件中的类产生类名冲突,导致打包失败。 ##### `ACComponentBase`简介 * `ACComponentBase`是组件入口的基类,所有的组件入口类都必须继承自此类。 * `ACComponentBase`拥有1个实例变量和1个实例方法 * 实例变量`appContext`是一个**弱引用**,指向`ACComponentBaseProtocol`协议,该协议包含系统的`UIApplication *applicationContext` `UIWindow *mainWindow` 实例对象; * 实例方法`initWithApp:`是默认的初始化方法。 * 程序启动时会调用组件初始化方法 * 组件入口子类可以覆写此方法进行自定义初始化设置,但必须调用父类的此方法。 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/06.png) ##### `ACRouter`路由简介 * `ACRouter` 是由`ACRouterKit.framework`提供的路由工具,是组件之间的通讯的桥梁,实现了组件之间不需要引用就可以交互的功能。 * `ACRouter`拥有一个实例方法和一个类方法。 * 类方法`+ (instancetype) route;`为ACRouter单例方法,创建路由必须通过该方法。 * 实例方法`- (id)openURL:(NSString *)URL toHandler:(ACRouterHandler)handler;` 组件之间通过该方法相互通讯。 ### 4.2 编写组件方法并调用 本小节示范了如何让一个ACComponentDemo2组件任意类去调用ACComponentDemo1组件入口类暴露的一个方法`showAlert:`并回调结果 * 在ACComponentDemo1类中实现一个方法`showAlert:` : ```objc -(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]; } ``` #### 组件入口类中实现供其他组件调用的方法的注意事项 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](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/07.png) * 入参`(ACRouterHandler)handler` 默认回调参数 #### 组件方法调用示例 * 在ACComponentDemo2工程中`ViewController1`类通过路由组件`ACRouter`调用ACComponentDemo1工程中组件入口类`ACComponentDemoHome`暴露出的方法`-(void)showAlert:(NSDictionary *)params;`,如下: ```objc [[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](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/09.png) * 在ACComponentDemo2工程中组件入口类`ACComponentDemoView`类中,提供tabbar子视图的方法如图: ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/10.png) ## 5. 生成组件包 ### 5.1 编译组件静态库.framework文件 * 关闭ACMobiNativeMain调试工程,然后打开ACComponentDemo1.xcodeproj。 * 选择 **`ACComponentDemo1` - `Generic iOS Device`**。 * 点击Product-Build,生成组件的.framework文件`ACComponentDemo1.framework`。 * 生成目录为ACComponentnDemo1/build/ACComponentDemo1。 * 新建component.xml和info.xml文件。 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/11.png) ### 5.2 编辑component.xml * component.xml记录了组件的入口类信息。其中plugin name是组件的入口类`ACComponentDemoHome`名称。 * 最终完成的component.xml示例如下 ```xml ``` ### 5.3 编辑info.xml * info.xml主要记录了组件的版本信息 * 由于组件也是插件的一种形式,因此info.xml格式与插件的info.xml格式基本一致 * 示例模板如下 ```xml ``` 其中`acName`替换成组件入口类对象名。x替换成当前组件的版本号(非负整数) * 然后向plugin节点中加入各个版本的简介,这些简介以倒序加入,由一个``节点和多个(可以为0个)``节点构成。 * ``节点记录了当前版本的简介 * ``节点记录了历史版本的简介 * ``节点记录了组件的简介 * `节点说明该库为组件 * **当组件版本更新时,应该将当前的``节点改为``节点,同时在其之前添加新的``节点** * 最终完成的info.xml范例如下 ```xml 1:版本更新记录 0:iOS组件范例 ``` ## 6. 其他开发说明 ### 6.1 引入第三库 * 第三方Library(.a文件),直接引入组件工程参与编译即可。 * 资源捆绑包(.bundle)和第三方静态framework(.framework),需要引入组件工程参与编译,生成组件包时也需要单独放入压缩包中,和组件.a处于同一目录下。 * 调试时,.bundle和.framework需要在调试主工程ACMobiApp中再引入一遍,否则会无法找到相应的文件。 * *建议组件工程将引入的.bundle和.framework文件添加到`Copy Files`的Build Phase中,这样可以在编译时将这些文件直接复制至`acComponentDemo`文件夹中* * 动态framework,目前暂不支持。 ### 6.2 组件如何引用资源文件 > 主要介绍了如何建立组件自己的资源捆绑包(.bundle文件)以供使用。 > 这里的资源文件包括但不限于xib,storyboard,png,jpg,json,xml,js,plist等文件 #### 6.2.1 生成组件资源捆绑包bundle文件的target * 打开组件静态库工程,然后点击菜单栏的File->New->Target, 在弹出的对话框中选择macOS->Framework & Library->Bundle ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/12.png) * *Product Name取名为ACComponentDemoBundle*,点击finish完成创建。 * 修改此target的Build Settings * 将`Product Name`的值修改为`ACComponentDemo1` * 将`Per-configuration Build Products Path`修改为`$SRCROOT/build/acComponentDemo1` * 将`Code Signing Identity` 修改为`Don't Code Sign` * 将`Combine High Resolution Artwork`修改为`No` * 将`Info.plist File`的值置为空 * 在工程的ACComponentDemoBundle目录下,找到info.plist这个文件,右键选择`delete`,然后选择`Move to Trash`以删除该文件 * 修改`ACComponentDemo1`这个target的Build Phases ,在`Target Dependicies`中添加bundle的target * 接下来,可以把组件需要的资源文件全部添加至`acComponentDemoBundle`这个target的Build Phases中的Compile Source即可 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/18.png) * 组件工程进行编译,查看结果。 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/14.png) #### 6.2.2 组件中包含xib文件处理方式 组件中如果有使用xib文件,需要将xib编译生成的nib文件放入6.2.1步骤生成好的bundle文件中使用,在Demo工程中具体使用步骤如下: *创建一个xib文件 *在xib上设置一个UIImageView控件,并在控件中设置图片资源tu_kefu.png * 接下来,可以把xib文件,和xib引用的图片资源文件全部添加至`acComponentDemoBundle`这个target的Build Phases中的Compile Source即可 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/18.png) *编译组件工程`acComponentDemoBundle`这个target,查看结果 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/19.png) #### 6.2.2 引用组件bundle中的图片资源文件 组件调用bundle中的资源文件示例如下 ```objc UIImageView *bgView = [[UIImageView alloc] initWithFrame:self.view.bounds]; bgView.image = [UIImage imageNamed:@"ACComponentDemo1.bundle/guide_2_0"]; [self.view addSubview:bgView]; ``` 代码示例如下: ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/16.png) ### 6.3 组件如何获取系统事件 #### 6.3.1 ApplicationDelegate事件 路由会将大部分ApplicationDelegate事件分发到每个组件入口类,组件入口类用相应的**类方法**接收即可。目前组件入口类可供接收的类方法有: ```objective-c + (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; } ``` #### 6.3.2 设置App启动页面 自定义组件中可以使用ApplicationDelegate系统事件设置app的入口界面, ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/20.png) *设置 [ACComponentMgr sharedComponentMgr].appDelegate.mainWindow.rootViewController 即可 ## 7. 自定义组件View 自定义组件ACComponentMyView提供了自定义View的创建方法,和对外暴露的设置属性路由 ### 7.1 自定义组件View开发 自定义组件view首先遵从组件的基本开发模式 ### 7.2 获取自定义view实例对象 可以通过路由的方法直接获取自定义view实例对象 路由方法示例: ```objective-c - (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]; ``` ### 7.3 通过路由方法设置自定义view的属性 可以通过路由的方法直接设置自定义view的属性 路由方法示例: ```objective-c -(void)setMyBtn:(NSDictionary *)params { ACMyButton *myBtn = params[@"myBtn"]; NSString *content = params[@"content"]; [myBtn setTitle:content forState:UIControlStateNormal]; } ``` 路由调用示例: ```objective-c 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需要声明可被订阅的消息,如下 ```objective-c ``` ### 8.2 订阅消息 通过路由暴露出的方法订阅消息, ```objective-c /** 监听模块信号 */ - (void)subscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject withAction:(SEL)listenerAction; ``` 其中moduleName为需要订阅对象的名称; signal为订阅消息的名称; Listener为订阅者; action为订阅触发的方法; 示例如下 ```objective-c - (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 发布消息 组件通过路由方法发布消息 ```objective-c /** 发布模块信号 */ - (void)publishModule:(NSString *)publishName withSignal:(NSString *)signal params:(id)data; ``` publishName为发布消息对象的名称; signal为消息的名称; params为传参; 示例如下: ```objective-c [[ACRouter route] publishModule:@"ACComponentMyView" withSignal:@"ACComponentMyViewSignal1" params:@{@"msg":@"btn已修改"}]; ``` ### 8.4其他 移除消息订阅 ```objective-c /** 移除信号 */ - (void)removeSubscriptionModule:(NSString *)moduleName Signal:(NSString *)signal Listener:(id)listenerObject; ``` moduleName为订阅对象的名称; signal为消息的名称; listener为订阅者; 示例如下: ```objective-c [[ACRouter route] removeSubscriptionModule:@"ACComponentMyView" Signal:@"ACComponentMyViewSignal1" Listener:self]; ``` ## 9. swift 组件开发 如果使用siwft 组件开发,务必遵循以下几点 * 组件库必须使用动态库 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/30.png) * 组件库名和组件类名需要保持一致 * ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/31.png) * 组件类必须加上`public`字段,组件暴露的方法必须加上`@objc`字段 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/32.png) ## 10. 组件上传(pod模式) ### 10.1 生成组件包 * 查看ACComponentDemo1目录下的build/acComponentDemo1目录。 * 将目录中的.framework,.a,.bundle文件,上传到私有pod仓库(见10.2) * 将上传到私有pod仓库的组件名称和版本号写入`info.xml`中 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/11.png) * 将`component.xml`,`info.xml`压缩成zip包 * 建议命名为`acComponentDemo1-ios-x.x.x.zip`, 例如:acComponentDemo1-ios-1.0.0.zip * 此zip包可以直接上传到打包服务使用。 ### 10.2 上传到pod私有仓库 * 首先创建podspec文件,在组件的根目录下输入下面的命令: ``` pod spec create acComponentDemo2(组件名称) ``` 出现.podspec文件,如图 ![image](https://gitee.com/ACCCCloud/AC-Component-Demo-iOS/raw/master/img/33.png) * 编辑.podspec文件内容 ``` s.name = "ACComponentDemo2" s.version = "1.0.2" s.source = { :git => "填写私有git仓库地址", :tag => "版本号" } s.ios.vendored_frameworks = "acComponentDemo2/ACComponentDemo2.framework", "acComponentDemo2/ACComponentSwift.framework", "acComponentDemo2/ACComponentListnerDemo.framework","acComponentDemo2/ACComponentMyView.framework" s.dependency 'ACEngineLib' ``` 其中的s.name和s.version,为组件名和组件版本,需要填写到上传组件的info.xml中; s.sourec 是私有git库地址; s.ios.vendored_frameworks 是组件库的相对路径,根据组件目录进行填写 s.dependency 'ACEngineLib' 组件依赖库的名称,组件依赖于引擎 *发布组件到pod仓库 ``` pod trunk push ACComponentDemo2.podspec ```