打一个比较形象的比喻,把APP比作我们的人体,把胳膊、大腿、心、肝、肺这些人体器官比作组件,各个器官分别负责他们各自的功能,但是他们之间也有主次之分,试想我们的胳膊、大腿等是不能独立完成某个任务的,必须需要心、肺、肝、胆等的能量支持,那么可以把胳膊、大腿这种功能性器官比作业务组件,把我们的心、肝、脾、肺、肾比作基础组件。 那么我们的业务组件必须要依赖于我们的基础组件才能发挥其应有的功能,我们的基础组件(心、肝、肺等)是高度复用的,胳膊、大腿等业务组件要解耦合,难道你的胳膊动,大腿也要跟着动吗~,最终由大脑整合(那么大脑可以类比成主工程)。 不知道您是否领会了精神,综上就是我们的组件化的思路。
继续我们上面那个惊悚的例子,但是我们把人变成机器人
~注:这里的稳定性指通俗地讲就是是否需要频繁修改代码~
综上所述我们项目组件化方案示意图可以是这样
如上图业务组件单向耦和于基础组件,这样的架构完成了基础组件的高度复用和业务组件的解耦。但是问题又来了,各个业务组件之间难免有页面跳转和数据交互,业务组件不耦合意味着不能直接调用,那么我们引入一个中间层。
这里注意,依赖一定是单项的,否则我们只是把融在一起的代码块拆分成多个代码块,而且比之前更麻烦了。
关于中间层实现众说纷纭,这里说下我们实践的方案。
主要分成四部分:
//运用的是swift中命名空间的概念,用运行时方法NSClassFromString获取到相应的类型 private static let routerModules:[String] = ["MessageProject.MessageProjectRouter", "IMProject.IMProjectRouter", "CommunityProject.CommunityProjectRouter", "CourseProject.CourseProjectRouter", "VideoProject.VideoProjectRouter", "QuestionbankProject.QuestionbankProjectRouter", "UserinfoProject.UserinfoProjectRouter", "CustomUIProject.CustomUIProjectRouter", "UIFrameProject.UIFrameProjectRouter", "BasicUIServiceProject.BasicUIServiceProjectRouter", "ActivityOperationProject.ActivityOperationProjectRouter"] private static var routerMap:Dictionary= [:] private static var methodMap:Dictionary = [:]
// 每个模块需要实现一个该协议的类,用于模块内部VC和method的注册 public protocol RegisterRoutersProtocol { static func registerModuleRouters() }
// VC注册,子类需要的话可以重写 @objc open class func registerRouterVC(_ routerURL:String) { guard let tempRouterURL = URL(string:routerURL) else { return } SDJGUrlRouterManager.registerRouterWithHandler(handler: { (transferURL:URL, transferType:SDJGTransfromType, sourceVC:UIViewController, userInfo:[String:Any]?, animated:Bool) -> UIViewController? in if transferURL.hasSameTrunkWithURL(tempRouterURL) { let viewCOntroller= self.init() viewController.setRouterInfo(userInfo: userInfo) if transferType == .push { if let nav = sourceVC.navigationController { // navController nav.pushViewController(viewController, animated: animated) } else { // modal nav vc sourceVC.modelVC(viewController, true, animated) } } else if transferType == .model { sourceVC.modelVC(viewController, false, animated) }else if transferType == .modelNav { sourceVC.modelVC(viewController, true, animated) } else { } return viewController } else { return nil } }, prefixURL: tempRouterURL) }
import Foundation import URLRouteProject //课程下载界面 public let kCourseFileDownloadURLString = "sina://router/downloadserviceproject/coursefiledownload" //资料下载界面 public let kDownLoadVCURLString = "sina://router/downloadserviceproject/download" class DownloadServiceProjectRouter: RegisterRoutersProtocol { public static func registerModuleRouters() { JCourseFileDownLoadVC.registerRouterVC(kCourseFileDownloadURLString) JDownLoadVC.registerRouterVC(kDownLoadVCURLString) } }
参数传递:为了有更多的类型参数可以传递,我们在router跳转方法里多加了一个参数,而不是用url拼接的方式,因为这样的话只能传递基本类型参数,像UIImage这种就无能为力了。
// 用于注册VC Router的闭包定义,会在页面跳转的时候执行闭包,参数为[String:Any]类型,这样参数就可以随意传了。 public typealias SDJGRouterHandler = (_ url:URL, _ transferType:SDJGTransfromType, _ sourceVC:UIViewController, _ userInfo:[String:Any]?, _ animated:Bool) -> UIViewController?
openURL的处理:我们为openURL提供了单独的方法跳转,其中包含了参数的解析。
cocoapods管理: 代码解耦只需要遵循上述原则就好,最根本的目的是业务组件的解耦,cocoapod的原理及使用在这里不在赘述(一搜一大堆)
正规的方式是 项目工程发布tag->配置本地podSpec文件并上传->校验->私有库发布->其他工程引入。 但在实际操作中有很多情况pod lib link由于种种原因会失败,而且发布私有库本身也需要时间,所以在依赖不变的情况下我们可以用其他的方式引入其他模块代码
//拉取对应commit代码 pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af' //默认拉取dev分支最新代码 pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :branch => 'dev' //拉取0.7.0tag的代码 pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git', :tag => '0.7.0'
直接修改对应提交的commit,这样同样缺点也很明显,需要 程序员 自己保证代码无误才可以提交,不会有pod的校验,所以这两点需要我们权衡。 为了提高效率,我们采用开发时提交commit,由各个业务负责人负责维护commit,每个版本发版时发布私有库的方式。
#社区项目 张三 pod 'CommunityProject',:git => 'http://172.16.117.224/ios-team/communityproject.git', :commit => '15407bae8eccafa14eab4d200e2a8ae763810f15' #用户信息项目 李四 pod 'UserinfoProject',:git => 'http://172.16.117.224/ios-team/userinfoproject.git',:commit => 'f1a408cd747e215f0b4fb08b4999edf00570c085' #活动运营 王二麻子 pod 'ActivityOperationProject',:git => 'http://172.16.117.224/ios-team/activityoperationproject.git', :commit => '432a21212fc5d6eed9d5d28eacb320e01ec9cc47' #课程项目 李六 pod 'CourseProject',:git => 'http://172.16.117.224/ios-team/courseproject.git', :commit => 'da10da98af8d53bfe15572958cef8d0cf5e5ba2a'
实际开发当中会遇到种种坑:flushed:如下
注意类和方法及属性的权限问题public、pravite等(swift、oc不用)
业务模块当然要有自己的测试入口,否则很多业务场景都没有入口,这就需要业务负责人自己添加自己的页面入口,这也是组件化之后的好处,每个业务组件都可以单独运行,单独测试,更加轻量级。
Unable to satisfy the following requirements:
这类问题是/Users/xingfan/.cocoapods/repos/master也就是cocoapod的本地索引库没有更新最新,里面没有Charts(3.1.0)版本的spec文件,导致它不知道去哪里拉代码。执行pod udate,一般这种问题都是嫌pod update太慢执行pod update --verbose --no-repo-update导致的
pod update会主动更新本地repo,如果报错,可以指定到本地spec仓库,一般在cd ~/.cocoapods/repos/iosspecrepo,然后git clean -f,如果再有问题,那就是组件间依赖出错,找相关负责人处理。
最后说说spec仓库,本身就是一个git仓库,pod repo update就相当于拉取并同步远程spec仓库(git pull),通过其中的spec文件(描述了目标源所在的地址、tag、依赖库的版本等)准确的找到想拉取的代码。
1.用cocoapods的缺点,代码集成到主工程后同样运行缓慢,原因是因为拉取的代码依然是需要编译的,本质上与原本没有区别。 针对这一点我们可以用Cathage替代cocoapods,CocoaPods (默认)自动建立和更新一个Xcode workspace,用来管理你的项目和所有依赖。Carthage使用xcodebuild来编译出二进制库,剩下的集成工作完全交给开发人员。模块变成可执行的二进制文件之后运行速度自然会快很多。 有兴趣的同学可以自行研究。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们