组件化探索之路

作者: 葱花思鸡蛋 | 来源:发表于2020-07-29 09:51 被阅读0次
    1、组件化理解

    组件化就是将APP拆分成各个组件(或者说模块也行),同时解除这些模块之间的耦合,然后通过主工程将项目所需要的组件组合起来。

    2、组件化的优缺点

    组件化的优点:
    1)组件可独立运行,提高的代码的复用性,组件化的颗粒度越细,可复用度就越高。
    2)当组件库的数量足够庞大时,项目只需要组合组件即可完成大部分的开发工作。
    3)组件化后项目的代码结构更加清晰,追踪问题、修复bug、增加需求更方便,不同业务组件相互独立,明确团队开发的业务边界,增加团队协作效率。

    组件化的缺点:
    1)增加开发人员的学习成本。
    2)增加了代码的冗余,组件化颗粒度越细,中间代码越多。
    3)增加了项目的复杂度,复杂度越高越容易出问题。
    4)总体上组件化对于项目的开发来说是利大于弊的,当然如果你的项目非常简单的话就没必要做这些了。

    3、组件化方案中间件选择

    对于中间件的设计方案,目前国内讨论比较火热的就两种。一是蘑菇街limboy大神的URLRoute+Procotol,另一种则是casatwy大神的Target-Action。

    相同点:
    1)这两种中间件方案都实现了组件对中间件单向依赖。
    2)结构基本一致,都将业务分成了调用方、中间件和服务方。

    不同点:
    服务方响应调用方的实现方式不同。

    URLRoute+Procotol:
    1)需要注册组件,通过注册组件使得服务方可以被中间件发现。
    2)调用方通过URL调用服务方页面,URL和服务方页面的关系通过路由表映射,路由表需要人工维护(硬编码),使用持续集成环境简化操作。
    3)调用方通过Procotol调用非页面类服务组件,可以传递复杂对象。

    Target-Action:
    1)不需要注册组件,通过runtime+约定命名规范(硬编码)的方式查找服务方。
    2)区分本地调用和远程调用,本地调用通过Target-Action获取服务,同时为远程调用提供服务,远程调用的规则需约定好。
    3)参数传递统一用Dictionary实现,获取Dictionary内所需要的内容需要通过文档或者其他说明。
    4)通过category的形式拆分中间件的代码,使其分属不同组件。

    4、组件化分层

    1)业务组件:是对业务模块的封装。
    组件化颗粒度一般指的就是这一层的封装颗粒度,理论上颗粒度做到每个页面是最理想的情况,但实际情况总是千变万化的,某些页面的耦合度可能会非常高,拆分的代价太大,得不偿失。那么我们完全可以将颗粒度稍微放粗一些,将有紧密业务联系的页面组成一个组件,然后暴露使用这个组件的接口即可。
    可以根据具体业务将各个模块,公用的模块或者功能,拆分出来作为单独的组件使用。

    2)业务库:自定义的控件、第三方库封装等等
    业务库虽然包含一定对业务逻辑,但是其中的业务逻辑应当是较大范围内都通用的业务逻辑,比如三方登陆、分享、支付库的调用业务。

    3)基础库:网络库、数据库、分类等。
    基础库中一定要避免混入业务逻辑,避免双向依赖。

    5、CTMediator的Target-Action分析
    中间件: CTMediator

    可以根据项目需要对中间建进行改造,完善自己的中间件,CTMediator满足基本开发需求。
    主要提供两种调用方法:
    1)本地模块调用:

    - (id _Nullable )performTarget:(NSString * _Nullable)targetName action:(NSString * _Nullable)actionName params:(NSDictionary * _Nullable)params shouldCacheTarget:(BOOL)shouldCacheTarget;
    

    调用方式:
    [[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]向CTMediator发起跨组件调用。
    CTMediator根据获得的target和action信息,通过objective-C的runtime转化生成target实例以及对应的action选择子,然后最终调用到目标业务提供的逻辑,完成需求。

    2)远程App调用

    - (id _Nullable)performActionWithUrl:(NSURL * _Nullable)url completion:(void(^_Nullable)(NSDictionary * _Nullable info))completion;
    

    调用方式:
    远程应用通过openURL的方式,由iOS系统根据info.plist里的scheme配置找到可以响应URL的应用(在当前我们讨论的上下文中,这就是你自己的应用),应用通过AppDelegate接收到URL之后,调用CTMediator的openUrl:方法将接收到的URL信息传入。当然,CTMediator也可以openUrl:options:的方式顺便把随之而来的option也接收,这取决于你本地业务执行逻辑时的充要条件是否包含option数据。传入URL之后,CTMediator通过解析URL,将请求路由到对应的target和action,随后的过程就变成了上面说过的本地应用调用的过程了,最终完成响应。

    组件:

    1)组件上传私有库通过cocoapods管理下载组件。
    2)组件要创建Target模块,定义target-action,提供给中间件调用。所有组件都通过组件自带的Target-Action来响应。
    也就是说,模块与模块之间的接口被固化在了Target-Action这一层,避免了实施组件化的改造过程中,对Business的侵入,同时也提高了组件化接口的可维护性。

    3)组件要创建Category模块,它是依赖中间件的分类,每一个Category对应一个Target,每个category里的方法对应了这个target下所有可能的调用场景,这样调用者在包含mediator的时候,自动获得了所有可用的target-action,无论是调用还是参数传递,都非常方便。

    详细介绍请参考博客:Casatwy的iOS应用架构谈 组件化方案 https://casatwy.com/iOS-Modulization.html

    5、私有库建立

    1)注册cocoapods 源码管理身份

    pod trunk register 邮箱 用户名
    

    2)查看注册信息

    pod trunk me
    

    可以查看你已经注册的信息,其中包含你的name、email、since、Pods、sessions,其中Pods为你往CocoaPods提交的所有的Pod!

    3)初始化本地仓库
    去码云或者GitHub上创建一个仓库,clone到本地。
    把要制作组件的demo存放到本地仓库,然后提交到远程仓库。

    4)将repo加入本地cocoapods库中

    pod repo add [私有Pod源仓库名字] [私有Pod源的repo地址]
    例如:
    pod repo add ShareUIRepo  https://gitee.com/conghuasijidan/MediatorProject.git
    
    // 查看repo list 
    pod repo list
    
    6、准备制作组件

    把要制作的组件文件都存放到一个文件夹,资源文件也放此文件夹中,可以用一个子文件存放资源。这样做是为了写入路径时方便,避免采坑。

    image.png

    Target 文件夹----虚拟目录
    文件命名规则:Target_组件名
    .h 不需要声明方法,因为方法是通过runtime匹配的
    .m 定义一些使用组件内容的action
    方法命名规则:Action_自定义方法名

    Category文件夹---虚拟目录
    基于CTMediator的分类,通过CTMediator的runtime方法调用Target中的action。
    文件命名规则:与组件名同名的CTMediator分类。
    action命名规则:自定义方法参数,实现对Target模块方法的调用。

    注意:不用把Target模块和Category模块一次性全部做出来,可以先把核心内容制作成组件,不加入中间件。成功之后再通过中间件一步步完成Target模块和Category模块。

    7、初始化podspec
    在项目同级目录下,创建podspec   
    pod spec create ShareUI  
    ShareUI:podspec 文件名
    
    编辑podspec ( 用Xcode打开)
    
    spec.name         = "Framework/Library的名字"
    spec.version      = "版本号"
    spec.summary      = "摘要"
    spec.description  = <<-DESC
                      描述
    spec.homepage     = "远程库的的存放地址"
    spec.authors      = { "作者" => "作者的邮箱" }
    spec.license      = { :type => "MIT", :file => "LICENSE" }       # 开源许可以及文件
     s.ios.deployment_target = "9.0"               # iSO最低支持版本
    spec.source                = { :git => "远程库的存放地址", :tag => s.version.to_s }  # 远端的资源文件
    spec.source_files          = "ConStaticLib/*.{h}"        # 资源文件
    spec.public_header_files   = "ConStaticLib/*.h"          # 头文件
    spec.requires_arc          = true                                                    # ARC模式
    spec.vendored_libraries    = "Libraries/ConStaticLib.a"      # 前置资源库
    spec.ios.vendored_libraries = "Libraries/ConStaticLib.a"     # 前置资源库
    spec.dependency "TMFProfile",          "~> 1.2.2"                    # 依赖的三方库
    
    // 段落主要填写内容详解
    <!--Spec Metadata-->
    spec.summary      = "跟name保持一致就行"
    spec.description  = "详细的描述,要比summary的摘要长"
    
    <!-- Spec License -->
    spec.license      = "MIT"   #直接写MIT就行
    
    <!--Platform Specifics-->
     spec.platform     = :ios, "9.0"   #支持的最低版本号
     
     <!-- Source Location-->
    spec.source       = { :git => "https://gitee.com/conghuasijidan/MediatorProject.git", :tag => "0.0.4" }
    # 远程仓库地址 以及版本号
    
    <!--Source Code-->
    spec.source_files  = "ShareUIDemo/ShareUIDemo/ShareUI", "ShareUI/**/*.{h,m}"
    #需要制作私有库的源文件路径
    source_files:这里要注意的是这里的路径是以xxx.podspec文件为根据,同级的话,就写文件名就好(一般要传pod
    的最外层文件夹和xxx.podspec文件是同级的)。往后多级的,逐级加。如果多个的话,就分组加,逗号隔开。
    
    <!--Resources-->
     spec.resources = "ShareUIDemo/ShareUIDemo/ShareUI/Resource/*"
     # 使用的资源路径,把资源文件放到要制作私有库文件夹下
    # 也有许多把资源文件制作成bundle的,但这时图片资源的使用方式要改变了
    
    <!--Project Linking-->
    spec.framework  = "UIKit"
    # 依赖的系统库
    
    <!--Project Settings-->
      spec.requires_arc = true
     # 使用ARC模式 
     
      spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" }
     # xcode 编译方式
     
     spec.dependency "JSONKit", "~> 1.4"
     # 依赖的第三方库
    
    8、校验podspec
    检查本地
    pod lib lint --allow-warnings --verbose --use-libraries
    --verbose可以显示详细的检测过程,出错时会显示详细的错误信息
    --use-libraries 过滤引用第三方报错
    
    检查远端
    pod spec lint --allow-warnings --use-libraries
    
    
    报错问题解决:
    error: include of non-modular header inside framework module
    
    1\. 引入类,使用@class XXX; 不能像平时一样直接引入.h,可以在.m文件中引入。
    
    2\. 引入协议,使用@protocol XXX; 。
    
    3\. 当需要继承别的文件时,按照@class XXX;引入会报错,此时只能引入.h文件。
    
    
    9、打上标签
    git add *
    git commit -m "提交内容"
    git pull origin master
    git push origin master
    git tag 0.0.2 && git push --tags
    
    
    10、将podspec 提交/更新到cocoapods管理
    将主项目添加(关联)到CocoaPods的目录下
    pod repo add 主仓库名  远端仓库地址
    
    <!--这一步一定要cd到podspec文件所在的文件夹-->
    pod repo push 主仓库名 子组件.podspec --verbose --allow-warnings --use-libraries
    
    pod repo push ShareUIRepo ShareUI.podspec --verbose --allow-warnings --use-libraries
    
    11、安装组件
    自己新建一个项目,安装组件,测试组件的使用是否正常。
    在 podfile 中添加:
    source 'https://gitee.com/conghuasijidan/MediatorProject.git'
    
    pod 'ShareUI' ,'~> 0.0.2'
    
    
    12、更新组件
    组件代码修改后,更新podspec,重复第七步到第十步的操作
    
    1)若报错连接不上github
    $sudo rm -fr ~/.cocoapods/repos/master
    $pod setup
    $pod install --repo-update
    
    2)安装组件时,若出错找不到新版本组件
    删掉索引缓存
    rm ~/Library/Caches/CocoaPods/search_index.json
    
    pod install --repo-update
    
    13、目录分级
    Pod::Spec.new do |spec|
    ...
    
    // spec 就是顶级目录
    
       spec.subspec 'ShareView' do |ss|
    // ShareView 自定义二级目录名
    
       ss.source_files = "ShareUIDemo/ShareUIDemo/ShareUI/XYShareUI.{h,m}","ShareUIDemo/ShareUIDemo/ShareUI/XYShareBtnView.{h,m}"
    // 二级目录要包含的文件    
       end
       
       spec.subspec 'Category' do |ss|
       ss.source_files = "ShareUIDemo/ShareUIDemo/ShareUI/CTMediator+ShareUI.{h,m}"
       ss.dependency "CTMediator"
    // 要依赖的第三方 
       end
       
       spec.subspec 'Target' do |ss|
       ss.source_files = "ShareUIDemo/ShareUIDemo/ShareUI/Target_ShareUI.{h,m}"
       ss.dependency 'ShareUI/ShareView'
    //  如果目录里文件有引用其他目录下的文件,要添加依赖文件的路径,注意避免相互引用
       
       ss.framework  = "UIKit"
       end
    
    

    分层后目录效果:


    image.png
    14、demo下载地址

    组件库地址:
    https://gitee.com/conghuasijidan/MediatorProject.git
    集成组件项目地址:
    https://gitee.com/conghuasijidan/MediatorDemo.git
    校验podspec报错指南:
    https://www.jianshu.com/p/f296ec3649f4

    参考文章:
    iOS组件化实践(一):简介
    https://www.jianshu.com/p/568e875abd48

    iOS 从零到一搭建组件化项目框架
    http://www.cocoachina.com/articles/25258

    iOS组件化实践(基于CocoaPods)
    https://www.jianshu.com/p/c625733f0692

    相关文章

      网友评论

        本文标题:组件化探索之路

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