iOS 组件化Object-c

组件化探索之路

2020-07-29  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读