美文网首页
iOS 组件化

iOS 组件化

作者: 小白进城 | 来源:发表于2019-11-11 17:05 被阅读0次

    理论篇

    什么是组件化

    组件化开发就是将一个臃肿的、单一的项目,根据功能/业务/技术等等进行拆分,形成一个个独立的功能组件,然后借助 CocoaPods 管理工具将其任意组合,集成一个完整的项目。

    你可以将 AFNetworking、SDWebImage 等等三方库理解为自己项目的一部分,属于基础组件部分,而我们要做的就是将项目划分成多个独立功能模块,再集成为一个完整的项目。这一过程看似多此一举,但是带来的优势却是非常大。

    为什么需要组件化

    项目初期,功能相对简单,普通的MVC+模块文件分割就可以满足绝大部分的需求。但是随着功能需求越来越多,业务越来越复杂多样,现有的架构已经不太适用了,即使使用了 Git 分支管理,依然经常发生合并冲突等等问题,另外后期的维护成本也大大增加,业务逻辑变得复杂、模块之间耦合度很大、查找问题效率变低、项目编译过程过慢…… 而且伴随着开发人员的增多(多个小组之间协作开发),这些问题尤为突出,优化开发结构变得非常重要。

    中间层

    针对上面的问题,第一个想到的优化就是新增一个中间层来协调各个模块之间的调用,所有的模块都通过这个中间层去实现调用和交互,但是这样虽然一定程度上降低了模块与模块的之间的耦合度,但是耦合都转嫁到了中间层上了,并且中间层的改动只能由一个人操作,否则非常容易发生冲突,本质上并没有发生多少变化。另外一点,查找问题的效率低下、编译过慢等问题依旧没有得到有效的解决。

    这是传统的中介者模式,这个中间层会依赖其他组件,其他组件也会依赖中间层完成服务。

    组件化

    组件化能够帮助我们将过大的项目拆解成数个小组件,开发者只需要关注于组件所依赖的其他组件,而无需关心完整项目的其他部分,每个组件可以自己采取所习惯的架构模式:MVC、MVVM、MVCS等等,就像开发一款个人独立的app那样自由。

    项目组件化之后所带来的好处是非常多的,我们先总结一下非组件化所造成的问题:

    非组件化:

    • 代码高耦合度、高依赖
    • 项目复杂、臃肿、编译过长(影响调试)
    • 难以融合/集成其他产品
    • 需要统一架构
      ……

    组件化:

    • 代码复用性提高,可方便的集成到其他项目
    • 项目可配置,方便集成和功能回退(指定版本)
    • 化整为零,将项目细小化
    • 方便组件的并行开发
    • 可方便做单元测试
    • 组件自由度高,即插即用
      ……

    当然组件化也有着它的缺点,对已有的项目实施组件化架构比较困难,耗费时间长,项目组成员需要一定学习成本;组件化并没有相应的标准,拆分的粒度要适中,拆分粒度过高,则让项目变得复杂,起到了反作用效果,反之,粒度过低,体现不了组件化的优势,在项目业务不断地添加的过程中,进行不断的尝试调整,找到适合自己项目的才是最好的。

    组件化的分层

    项目组件化中,最难把握的就是粒度问题,这需要开发的自己的经验去把控。这里只给出个人认为的层次的划分。

    【基础组件】:宏定义/常量/自定义工具类,如常用的自定义分类
    【功能组件】:项目中所用到的功能,如地图定位/消息推送/分享等
    【业务组件】:项目中的模块/业务,如聊天室/直播间/个人中心等
    【中间组件】:负责项目中的路由/消息通知/传参/回调等
    【宿主工程】:项目容器,用来集成组件,调整各个组件之间的消息传递的容器。

    中间层的几种方案

    在组件化过程中,中间层是各个组件的通信的桥梁,中间层在组件化过程中扮演着非常重要的角色。目前关于中间层的设计笔者已知的有以下三种方式:基于URL Scheme的路由、基于Runtimetarget-action、面向接口。

    • 路由

    iOS 中支持的 URL Scheme 让我们能够在应用之间、应用内部传递消息。日常开发过程中经常用到的就是调用系统服务、唤起三方app等等,这些属于应用之间的消息传递,而我们这里借助 URL Scheme 完成应用内部的消息传递。这里的路由 URL 遵循网上通用的资源标识符合 URI,如:appscheme://home/scan?param=value,我们通过 URL 来传递信息,下层服务方通过 URL 获取参数提供服务,上层消费者通过 URL 获取到服务,完成调用。

    基于 URL Scheme 的三方库

    JLRoutes 是一种基于 URL Scheme 的路由框架,它全局会保存一个Map,key 是路由协议 url,value 则是对 url 解析后 block 回调,你可以在该回调中处理具体的业务。

    实例:

    例如我们的路由协议定义如下:

    scheme://描述/打开方式/保留字段/功能标识?参数1=值1&参数2=值2
    ||
    myroute://market/1/route/cjpm?stockcode=600212.ss
    

    首先配置路由 url 和 对应的回调处理:

    /// 默认下都会进入这里,这里填写路由匹配规则
    [JLRoutes.globalRoutes addRoute:@"/market/:operate/route/:code" handler:^BOOL(NSDictionary<NSString *,id> * _Nonnull parameters) {
        NSLog(@"%@", parameters);
        // 接下来的业务逻辑
        return YES; // 返回YES,表示处理截止,后面的路由规则不再启用
    }];
    

    然后在需要路由的地方传入相应的路由 url :

    // 某地方获取到的url
    NSURL* url = [NSURL URLWithString:@"myroute://market/1/route/cjpm?stockcode=600212.ss"];
    // 处理路由
    [JLRoutes routeURL:url];
    

    基于Runtimetarget-action

    相比 url scheme 的提前注册、实现服务,CTMediator 借助 OC 运行时的特性,现实组件之间服务的自动发现,无需提前注册即可实现组件间的调用,因此,这种方案的可维护性、可读性、扩展性相对较高。

    官方的 Demo 中,结构是这样的:

    Demo 结构

    CTMediator 的使用流程大体是这样的:

    底层组件

    1. 创建 Target_ 开头的目标类,如Target_A(该类是为了让中间件 CTMedator 通过 NSClassFromString生成类),类中定义 Action_ 开头的可调用的方法(为了让中间件 CTMedator通过 NSSelectorFromString 生成方法器),并且这些方法都有一个字典类型参数接收调用者传递过来的信息。

    2. 创建 CTMedator 的分类(方便扩展、分块),此分类对应着Target_A,分类中定义该组件对外(调用者)开放的 API 方法,该组件的开发者需要使用 CTMedator 的核心方法 performTarget:action:params:shouldcacheTarget: 完成方法调用。

    上层组件

    导入对应 CTMedator 的分类,完成方法调用。

    相比传统的中介者模式,这种 target-action 方案解放了中间件对其他组件的依赖,因为它是通过 NSClassFromStringNSSelectorFromString 来生成类的实例和方法器SEL的,然后介入消息的分发机制完成消息分发的,即所谓的主动发现服务。传统的中介者模式中,中间件和其他组件是双向依赖的:

    传统的中间层,图来自网络

    target-action 方式则是单向依赖,这样做的一个好处就是降低了一定的耦合,在我们移除某个组件时,中间件无需进行改动。

    使用 CTMedator 之后,图来自网络

    那么,由于没有引入具体的类,而是通过字符生成对应的类和方法,那么关于 CTMedator 的分类要清楚的知道 Target_ 类以及其中的内容。

    CTMedator 的分类可以划分为一个组件,必要时,集成到项目中进行调用。

    • 面向接口 Protocol

    在路由和 target-action 方案中,都存在硬编码问题、参数不明确问题:URLTarget_Action_ 的硬编码,参数都是通过字典的形式传递,类型不明确。

    面向接口 的方式能够很好的解决这两个问题。面向接口的方案通常由两部分组成,一个是用来管理接口协议的类(ModuleManager),一个是具体的接口协议(ComponentProtocol)。

    ModuleManager 负责消息的调用和转发,它内部需要存储一张映射表,完成 Protocol -> Class 的工作。ComponentProtocol 文件定义了业务组件可以提供的功能服务,可以将所有服务都定义到其中,也可以按组件划分。这样所有调用方只需要依赖中间件,不需要依赖其他的组件,而中间件通过接口协议绑定可以用于服务的类,即每个组件有一个用于实现对外提供的接口协议的类。在编译时,将对应的类注册到ModuleManager 中,Protocol 的名称即为查找的 key。

    注册绑定:

    [ModuleManager registerClass:User forProtocol:@protocol(UserProtocol)];
    

    调用时通过接口协议从 ModuleManager 中映射出注册的 Class,将获取到的 Class 实例化,并调用协议方法完成服务调用。

    Class cls = [[ModuleManager sharedInstance] classForProtocol:@protocol(UserProtocol)];
    NSObject <UserProtocol> *user = [[cls alloc] init];
    NSString *userName = [user getUserName];
    

    接口协议的方式虽然可以很好的解决参数类型的不确定性,硬编码问题(实现部分可以任意替换),但是它不是前面两种的替代品,因为他们都有自己的侧重点,如 路由URL 可以在应用之间实现消息传递,面向接口可以用来为某类添加功能或者对类进行功能约束等。

    一些注入框架是支持面向接口的注入的,可使用这些库取代 ModuleManager 类。

    小结

    三种方式都分为底层服务方和上层使用方,服务方都对外提供 了服务媒介,CTMediator 中是 Target_A 文件,面向接口就是 Protocol,路由 URL Scheme 则是回调 block

    在三种方式中,个人觉得最不推荐的是 CTMediator 方案,感觉很是臃肿,虽然可以通过多个分类去定义组件,但是实际上对底层组件的调用逻辑都耦合在了中间件中,这意味着中间件需要频繁的进行更新,另外存在太多的硬编码地方,targetaction以及参数名都是硬编码在中间件中的,这样的方式并不灵活。但是 CTMediator 中通过运行时解耦了中间件对底层组件的依赖,以及去 model 化的想法还是非常好的。

    面向接口 Protocol 的方案贯穿了底层组件、中间件以及上层组件,一方面解耦了中间件对底层组件的耦合,底层组件变得透明,可以根据接口协议任意替换,另一方面接口协议还确定了参数类型。但是该方案面向的是应用内部的功能通信,外部调用应用时,还是需要路由或者硬编码的形式完成。

    路由定义了一套用于信息传递的标准,通过路由,服务方可以注册并实现符合某种特定条件的服务,使用方则通过中间件传递 一条URL 来调用该服务。服务方和使用方彼此透明,可以任意替换。和接口协议比起来,路由的可以处理本地内部和远程外部的两种类型的调用,缺点是 url 需要硬编码,而且参数类型都是字符。路由 URL 和接口协议都需要提前注册才能使用,路由需要 block,接口协议需要 class

    路由和接口协议并不冲突,可以使用路由 + 协议的方式来实现中间件,路由实现外部的调用,应用的降级处理等,组件之间通过接口协议来定义功能服务,这样组件内部可以在迭代中方便的替换实现类。

    核心工具 CocoaPods

    组件化架构,需要一个宿主工程,负责集成所有的组件。每个组件都是一个单独的工程,通过 Git 私有仓库来管理。这样拆分工程项目,开发人员只需要关注与组件相关的部分,而不用考虑其他组件,新人上手更容易。

    组件化集成

    所有的组件都上传到 Git 仓库并支持 Cocoapods 集成。主工程通过配置 Podfile 文件,然后一键 pod update 即可。使用 Cocoapods 来管理组件主要因为其本身功能强大,方便的集成整个项目,解放对依赖库的管理。使用组件化的集成方式,可以很好的避免传统项目中的代码冲突问题。

    核心命令:

    # 安装命令
    sudo gem install cocoapods
    # 配置
    pod setup
    # 通过Podfile安装三方库
    pod install
    # 通过Podfile更新安装三方库
    pod update
    

    Git 是一个分布式版本控制系统,能够快速高效地处理从小型到大型项目的所有内容。Git 官方文献资料

    当然,如果不想记住这些命令,你可以借助市场上的热门开发工具,这里推荐 Git 官方桌面端Sourcetree

    Xcode 本身就支持项目的 Git 仓库管理,在 Source control 中就可以创建管理你的项目。

    image.png

    在你创建项目时,Xcode 就提示你是否创建 Git 仓库:

    默认创建Git 仓库

    这里需要注意的就是 podspec 索引文件的编写。

    Pod::Spec.new do |s|
      s.name             = '组件工程名'
      s.version          = '0.0.1'
      s.summary          = '简介'
      s.homepage         = '远程仓库地址'
      s.license          = { :type => 'MIT', :file => 'LICENSE' }
      s.author           = { '作者' => '作者' }
      s.source           = { :git => '远程仓库地址', :tag => s.version }
      s.ios.deployment_target = '8.0'
      s.source_files     = 'Classes/**/*.{swift,h,m,c}'
      s.resources        = 'Assets/*'
      s.dependency 'AFNetworking', '~> 2.3'
      s.dependency 'Reachability','~> 3.2
    end
    

    source_files:是你要共享的文件
    resources:是一些资源文件,比如图片资源
    dependency:是该组件所需要依赖的其他组件、三方组件等。

    关于创建子模块/子文件夹

        //简单:
        subspec 'Twitter' do |sp|
            sp.source_files = 'Classes/Twitter' //指定子模块路径
        end
        subspec 'Pinboard' do |sp|
            sp.source_files = 'Classes/Pinboard'
        end
        
        //复杂:
        Pod::Spec.new do |s|
            s.name = 'RestKit'
    
            s.subspec 'Core' do |cs|
                cs.dependency 'RestKit/ObjectMapping'
                cs.dependency 'RestKit/Network'
                cs.dependency 'RestKit/CoreData'
            end
            s.subspec 'ObjectMapping' do |os|
            end
        end
    

    更多内容参考 基础-podSpec使用

    典型的产品

    • 滴滴

    滴滴的组件化是将项目拆分为业务部分和技术部分,业务部分包括专车、拼车、巴士等组件,使用一个 pods 管理,技术部分则分为登陆分享、网络、缓存等基础组件,分别使用不同的 pods 管理。

    组件间的通信通过 ONERouter 中间件进行通信,中间件担负协调和调用各个组件的责任。组件间通信通过 OpenURL 方法来进行对应的调用。ONERouter 内部保存一份 Class - URL 的映射表,通过 URL 找到 Class 并发起调用, Class 的注册放在 +load 中进行。

    • 淘宝

    淘宝架构的核心思想是一切皆组件,将工程中所有代码都抽象为组件。在 CocoaPods 中可以通过 podfile 很好的配置各个组件,包括组件的增加和删除,以及控制某个组件的版本。

    淘宝架构的主要分为四层,从上到下依次是:业务组件 -> 核心层 /容器-> 中间件/功能封装 -> 底层库。容器是整个架构的核心,负责组件间的调度和消息派发。

    总线设计:URL 路由 + 服务 + 消息。统一所有组件的通信标准,各个业务间通过总线进行通信。

    URL 路由

    路由 URL 统一对三端的行为进行了统一,一套 URL 就可以调起 iOS、Android、前端三个平台的对应组件。

    URL 路由请求可以被解析就直接调起相应的组件,如果不能被解析(没有对应的组件)就跳转 H5 页面,这称为降级处理。

    服务

    服务提供一些公共服务,是面向接口的,通过接口协议 Protocol 进行调用。

    消息

    URL 路由通常都是一对一进行通信,那么针对一对多的消息派发和调度就可以通过消息完成,这类似于 iOS 的通知机制。例如应用的前后台切换、Socket的推送消息等,都可以通过消息机制分发到接收消息的组件。

    小结

    我们可以看到,滴滴和淘宝的组件化上有很多的相似之处,组件化的核心工具 CocoaPods,URL 路由进行页面的路由跳转,其他的如接口协议、消息通知等,应该都有类似的解决方法。除了管理组件的核心 CocoaPods 工具,URL 路由、接口协议服务、消息通知等都是我们在组件化过程中使用到的利器。

    总结

    组件化开发就是将项目进行拆分成一个个独立的功能组件,然后将其组合成一个完整的项目。那么,如何拆分?组件如何通信?如何组合?都是我们要考虑的问题。关于分层和拆分的粒度都没有标准化的,需要开发者根据以往已经合理的进行规范。组件间的通信有多种方式,这里比较推崇淘宝的架构,路由 + 服务 + 消息的形式实现多种方式的通信。组件化的核心工具就是 CocoaPods ,我们要做的就是将组件项目上传到 Git 或者码云,编写项目的 podSpec 文件让组件支持 CocoaPods 集成 即可。CocoaPods 的功能十分强大,即使非组件化项目,我们同样使用它来管理依赖库,安装、卸载、升级、降级等,只需要一个命令即可完成,作为开发者,这个工具是必定要掌握的。

    参考


    实践篇

    上一章节中,我们简单介绍了以下组件化的概念、使用到的工具等,这一章节中我们来演示一个组件如何制作。

    组件的创建

    首先我们来为项目创建一个关于网络请求的功能组件 LLNetworking

    • 拉取模版

    我们将创建在桌面上的一个名为 Demo 文件夹中。通过终端进入到该文件夹下,然后输入命令:

    pod lib create LLNetworking
    

    这个命令会为了拉取 Pod 的 基础模板。拉取之后,还会通过询问的形式为你配置一些东西:

    // 作用的平台
    What platform do you want to use?? [ iOS / macOS ]
     > iOS
    
    // 语言环境
    What language do you want to use?? [ Swift / ObjC ]
     > ObjC
    
    // 是否需要一个 demo 用来测试组件
    Would you like to include a demo application with your library? [ Yes / No ]
     > Yes
    
    Which testing frameworks will you use? [ Specta / Kiwi / None ]
     > None
    
    Would you like to do view based testing? [ Yes / No ]
     > Yes
    
    // 组件中,文件的前缀
    What is your class prefix?
     > LL
    

    确认之后,系统会为你自动配置组件项目,创建好的项目如下:

    LLNetworking
    • Example 工程

    项目文件目录中存在一个名为 Example 的工程,这个工程是你选择 Would you like to include a demo application with your library? 中选择 Yes 时为你添加的,这个还是很有用的,在你开发过程中可以通过它来集成测试组件功能的正确性、完整性。 我们先打开这个 Example 来看下:

    项目结构

    这个 Example 已经为你的组件创建了索引文件 podspec,并且集成了该组件。我们来看下 ExamplePodfile 的内容:

    use_frameworks!
    
    platform :ios, '8.0'
    
    target 'LLNetworking_Example' do
      pod 'LLNetworking', :path => '../'
    
      target 'LLNetworking_Tests' do
        inherit! :search_paths
    
        pod 'FBSnapshotTestCase'
      end
    end
    

    其中为你集成了一个测试用例 pod 'FBSnapshotTestCase',目前可以忽略。

    我们可以看到: pod 'LLNetworking', :path => '../' 这一句,path 路径指向了本地路径,对应 LLNetworking 主目录下:

    组件目录

    这个文件夹下,一个存放你的各种类文件,一个存放图片资源等。

    • podspec 文件

    在你回答之前问题之后,pod 为你自动创建了该文件,并执行了 pod install 命令,该命令会找到组件的索引文件(也在本地) LLNetworking.podspec

    #
    # Be sure to run `pod lib lint LLNetworking.podspec' to ensure this is a
    # valid spec before submitting.
    #
    # Any lines starting with a # are optional, but their use is encouraged
    # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
    #
    
    Pod::Spec.new do |s|
      s.name             = 'LLNetworking'
      s.version          = '0.1.0'
      s.summary          = 'A short description of LLNetworking.'
    
    # This description is used to generate tags and improve search results.
    #   * Think: What does it do? Why did you write it? What is the focus?
    #   * Try to keep it short, snappy and to the point.
    #   * Write the description between the DESC delimiters below.
    #   * Finally, don't worry about the indent, CocoaPods strips it!
    
      s.description      = <<-DESC
    TODO: Add long description of the pod here.
                           DESC
    
      s.homepage         = 'https://github.com/LOLITA0164/LLNetworking'
      # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
      s.license          = { :type => 'MIT', :file => 'LICENSE' }
      s.author           = { 'LOLITA0164' => '476512340@qq.com' }
      s.source           = { :git => 'https://github.com/LOLITA0164/LLNetworking.git', :tag => s.version.to_s }
      # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
    
      s.ios.deployment_target = '8.0'
    
      s.source_files = 'LLNetworking/Classes/**/*'
      
      # s.resource_bundles = {
      #   'LLNetworking' => ['LLNetworking/Assets/*.png']
      # }
    
      # s.public_header_files = 'Pod/Classes/**/*.h'
      # s.frameworks = 'UIKit', 'MapKit'
      # s.dependency 'AFNetworking', '~> 2.3'
    end
    

    该文件为你的组件自动配置了一些基本的信息,因为我之前使用过 trunk 登陆过,所以这里有的的账号信息。当然这些信息是需要你根据情况修改的,更多的配置你可以搜索相关文档。

    注意:这里的 Git 地址目前是找不到的,后期需要自己关联。

    设置共享文件

    podspec 文件中 s.source_files = 'LLNetworking/Classes/**/*' 指代共享的资源路径,我们需要将共享的文件放到这里来。

    共享文件夹

    我们打开组件的目录查看,可以看到这里已经有了名为 ReplaceMe 的文件了,这暗示你用共享文件替换它。

    podspec 文件中还有一个被注释掉的:

    # s.resource_bundles = {
      #   'LLNetworking' => ['LLNetworking/Assets/*.png']
      # }
    

    这个目录中存放一些图片等资源,当你需要的时候可以开启来。

    我们来创建一个 LLNetworking 类:

    @interface LLNetworking : NSObject
    -(NSString*)getSomething;
    @end
    
    @implementation LLNetworking
    -(NSString *)getSomething{
        return @"test method.";
    }
    @end
    

    将其移动到组件的共享目录下并删除掉空文件ReplaceMe

    设置共享文件

    这样,我们就设置好了共享的内容,即组件开发好了。接下来,我们使用 Example 工程来使用这个组件的内容。

    终端进入 Example 工程目录下,执行 pod install 命令来安装组件。

    安装新的组件

    我们发现,Example 项目中 Pods/Development Pods/LLNetworking 下,多出来最新添加的文件。

    使用组件

    我们安装好组件之后来使用一下组件的功能,就像使用三方库那样:

    -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        LLNetworking * networking = LLNetworking.new;
        NSLog(@"%@",networking.getSomething);
    }
    

    控制台输出:

    2019-11-08 17:14:47.455341+0800 LLNetworking_Example[7038:1682304] test method.
    

    这表示功能正常。

    在组件开发过程中,使用 pod 'LLNetworking', :path => '../' 将路径指向本地是很有必要的,方便测试你的组件配置是否正确,功能是否完善,相比推到远程、发布再集成,这方便太多了。

    三方依赖库

    有时候,我们的组件还依赖其他的组件,又或者是三方库。我们通过 s.dependency 字段去设置,多个库可以分开写多次。

    Podfiles 模版里最后一条已经为我们添加好了,所依赖的是 AFNetworking ,正好是我们网络请求组件所依赖的,我们把它开启,重新 pod install

    Analyzing dependencies
    Fetching podspec for `LLNetworking` from `../`
    Downloading dependencies
    Installing AFNetworking (2.7.0)
    ……
    

    我们发现,Example 自动拉取了组件 LLNetworking 所依赖的其他组件。CocoaPods 工具的另外一个优点就是,多个组件依赖同一个组件时,它会自动帮你检测安装,而不会重复导入。

    本地组件和远程组件

    我们发现 Example 工程的 Pods中,本地开发的组件和远程发布的组件被分别放在了不同的目录下。

    有了 AFNetworking 之后,你就可以修改你的网络请求组件了:

    #import <AFNetworking/AFNetworking.h>
    @interface LLNetworking : NSObject
    @property(strong,nonatomic)NSURLSessionDataTask *task;
    - (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure;
    @end
    
    @implementation LLNetworking
    - (NSURLSessionDataTask *)POSTWithURLString:(NSString *)URLString parameters:(id)parameters success:(void (^)(id responseObject))success failure:(void (^)(id error))failure{
        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        manager.requestSerializer.timeoutInterval = 20;
       _task =  [manager POST:URLString parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
           if (success) {
               success(@{@"status":@"success"});
           }
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            if (failure) {
                failure(@{@"status":@"failure"});
            }
        }];
        return _task;
    }
    @end
    

    修改好之后,还不能直接在 Example 中使用,需要卸载组件再重新安装。注释掉 pod 'LLNetworking', :path => '../' 之后执行 pod install 即可完成卸载。

    至此,你完成了组件的创建、文件共享、本地化测试使用和更新。但是,我们的组件毕竟是要服务于宿主工程的,如果仅仅只能是通过本地集成,那意义不大,我们要将其关联到远程服务器,推送到本地搭建的 GitLab,又或者是 GitHub码云Coding 等平台。

    关联远程仓库

    在模版 podspec 文件中,已经帮我们指定了一个 GitHub 的仓库地址,

    s.homepage         = 'https://github.com/LOLITA0164/LLNetworking'
    

    你可以使用它或者进行修改它。我们这里选择使用它,先去 GitHub 创建对应的仓库。

    创建演示demo

    在最初创建组件时,系统已经帮我们创建好了本地 Git 仓库,进入到项目中,显示出隐藏文件夹就可以看到(command+shift+. 显隐):

    本地git仓库

    如果没有,你可以使用命令 git init 创建一个。现在,我们要将之前的修改进行提交(本地提交)。

    git commit -am "第一次提交"  
    

    然后我们要把本地的 Git 仓库和刚刚创建的远程仓库进行关联。如何关联呢?你在网站上创建项目后有了这样的提示:

    关联远程仓库

    这里有三种:创建一个新的仓库,推送一个已存在的仓库以及从其他仓库导入。我们这里使用第二种即可。

    添加远程仓库:

    git remote add origin https://github.com/LOLITA0164/LLNetworking.git
    

    将本地内容推送到远程仓库:

    git push -u origin master
    

    可能会出现让你登陆验证,输入你的用户名和密码即可。出现以下信息即表示推送成功。

    remote: Resolving deltas: 100% (49/49), done.
    To https://github.com/LOLITA0164/LLNetworking.git
     * [new branch]      master -> master
    Branch 'master' set up to track remote branch 'master' from 'origin'.
    

    回到 GitHub 刷新一下即可看到你的提交记录。

    上述是通过终端命令进行 git 操作,如果你并不熟悉 git 命令,你大可以使用便捷的可视化工具(上一章节有所提及),仅需简单的点击操作即可完成项目的管理。

    tag 并发布到 Cocoapods

    打标签

    至此,我们已经成功的将本地仓库关联并推送到远程仓库,现在我们要发布一个可用的组件。

    首先我们要给当前项目打一个 tag 版本号,在 podspec 中:

    s.version          = '0.1.0'
    

    指定的版本号是 0.1.0,那么我们就同样打个 0.1.0tag:

    $ git tag 0.1.0  // 打 tag
    $ git push --tags  // 推送到远程
    

    tag 默认在当前分支上,因为这里只有 master,所以不用切换分支,如果后期有其他分支,注意别弄错了。

    刷新页面,项目的 release 选项中会出现刚刚打的版本。

    打tag

    你也可以直接在页面的 release 下添加新的 tag,点击 release 可以看到编辑页面:

    在线打包

    发布到 CocoaPods

    由于我们创建的项目以及标签的版本号都是沿用了 podspec 文件中的信息,因此可以直接验证 podspec 文件信息是否可以通过验证,如果需要调整,调整之后最好同样先验证:

    pod spec lint
    

    podspec 文件的版本号一定要和 tag 保持一致。

    如果通过验证,那么你会看到类似下面的提示,绿色的 passed validation

    验证通过

    现在可以将 podspec 文件提交到 CocoaPods 上了:

    首先要通过 trunk 注册生成一条会话:

    // pod trunk register 邮箱 用户名 描述
    pod trunk register 476512340@qq.com LOLITA0164 --description=组件化demo 
    

    然后去邮箱进行验证,验证成功会出现下面页面:

    验证通过

    现在,就可以将 podspec 提交给 CocoaPods 了。这个文件将是别人搜索你的组件的索引。

    pod trunk push LLNetworking.podspec --allow-warnings
    

    上传完成之后,接可以通过 pod search LLNetworking 搜索到自己的组件了,如果搜索不到,删除本地的搜索文件,命令 :

    rm ~/Library/Caches/CocoaPods/search_index.json
    

    重新 search 产生新的搜索文件。

    发布新版本则需要打新的 tag,重新编辑 podspec 文件,然后再次提交给 CocoaPods

    集成到宿主工程

    我们已经完成了网络组件的创建和发布,也支持了 CocoaPods 的集成。现在我们需要将该组件集成到宿主工程中去,这部分没什么好提的,因为使用方式和集成三方库是一样的,可以说三方库只不过是他人编写的功能组件而已,我们的组件同样可以提供给小组成员使用,相比于纯粹的三方库,我们的许多组件都关联了业务部分,或者基于私人的其他组件,因此适用范围较小。

    小结

    本章节先介绍了如何通过 pod 的模版工程创建组件,组件的配置,集成本地组件,然后介绍了远程仓库的关联,支持 CocoaPods 的集成等内容,学会了这些,你就可以将自己得意的功能库提供给他人使用。在组件化的过程中,Git 是我们必须要掌握的,即使你不会使用命令,但是一定要熟悉相关的软件。

    相关文章

      网友评论

          本文标题:iOS 组件化

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