打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
iOS 多模块管理
userphoto

2017.01.05

关注

为了增强代码复用性和屏蔽其他模块的影响等因素,往往会采取将一个iOS项目拆解成多个子工程的方式,每个模块一个子工程。然后用一个大的工程胶合起来。这样子模块在其他的项目中还可以复用。有些团队为了团队之间代码维护方便,也会采取这样的策略。甚至,把每个模块做为一个单独的repo来管理。这样单个项目中就会有多个工程。就拿最近业务做得项目来说已经拆成了60多个工程。而这60多个冲程都是不同的git repo。再加上朱工程,大概有几十个git repo了。管理起来最开始也很是痛苦。于是就有了下面所诉述的探索管理多模块的路程。


最开始的时候是单一工程,所有的代码都堆积在一个工程里面。模块的共享基本上靠手动拖拽。因为我打算把其中一些比较好的工具开源出去。所以很多功能都是单独拆解的。每次同步在这些工具中的修改就非常痛苦,每次都是beyondcompare。文件比对,git的功能完全用不上啊。


为了能够用上git,所以我进行了拆目录的处理。虽然xcode的工程还是只有一个,但是打算开源出去的工具所在的目录都是一个git repo。xcodeproject只是对对应目录下的文件进行了相对引用。这样就组成了:

> 单xcode工程,相对引用源文件,多git repo的结构


可是,每次手工的去同步不同的git repo真心是个繁重的体力活。


为了能够对拆解出来的git repo进行有效的管理,避免每次手工维护。于是开始使用git submodule功能。这个真是从入门到放弃,大概只是用了一个月的时间。这段时间虽然git submodule比手工管理git repo方便多了,但是还是有很多问题。可以参考这片文章。参考[Git submodule的坑](http://blog.devtang.com/2013/05/08/git-submodule-issues/)。


这个管理的工具链还得继续演进。正好这个时候我打算把其中的一些工具和项目进行开源,要进行cocoapod发布。就顺便对这些工程进行了cocoapod的兼容。这个过程中,意外的发现原来cocoapod还有个开发模式!!!可以相对路径引用所有的源文件。这个简直就是梦幻般的功能啊。于是我毫不犹豫的进行了大刀阔斧的改造。 把主工程拆成了一个空壳。其中的业务逻辑代码按照功能进行业务模块进行拆解。并且将业务无关的工具,进行了抽离组成单独的模块。如下图所示。



~~~

├── Bizs

│   ├── NetContants

│   ├── YHActionGroup

│   ├── YHAppearance

│   ├── .......

├── Core

│   ├── Podfile

│   ├── Podfile.lock

│   ├── Pods

│   ├── Rakefile

│   ├── YaoHe

│   ├── YaoHe.app.dSYM.zip

│   ├── YaoHe.ipa

│   ├── YaoHe.itmsp

│   ├── YaoHe.xcodeproj

│   ├── YaoHe.xcworkspace

│   ├── YaoHeTests

│   ├── YaoHeUITests

│   ├── dsymtool

│   ├── fastlane

│   └── hive_pod.rb

├── Libs

│   ├── CTFeedback

│   ├── DZAccountFileCache

│   ├── DZAdjustFrame

│   ├── ......

├── Project

│   ├── Readme.md

│   └── default.xml

└── tools

    └── cassowary

~~~


1. Core是主工程,现在里面基本上就是个空壳。没有任何业务相关的代码,只有一些全局共享的资源还有工程配置文件。

2. Bizs是业务逻辑部分,每个子目录都是一个单独的业务模块。这些业务模块都是一个个的Podspec。

3. Libs是打算开源出去的代码。其中也包含一些第三方开源的Github项目,因为可能会涉及到修改,也放在里面了。之所以将Bizs和Libs进行隔离,也是按照机制与策略分离的思想,把业务逻辑放一堆,把机制部分放一堆。

4. Project是为了适配Google Repo工具的配置文件。

5. tools下面放一些脚手架。



将主工程拆成空壳之后,势必有一个问题是:如何再将所有的源码胶合进来。这里就用到刚才提到的Cocoapods的开发模式。因为将Bizs和Libs下面的子模块都做成了单独的podspec。所以我在Core下面的Podfile对其进行相对路径引用:


~~~

pod 'xxx', :path=> '../Bizs/xxx'

~~~


这样就完成了模块的拆解与胶合。而在这里每一个模块都是一个git repo,累积下来也得有60多个了。其实原先的git submodule的方式也是用的我心累。于是就在网上搜这方面的工具,尝试了git slave之类的东西,都不是很顺手。最后意外的发现了Android源码管理工具**repo**这个神奇的工具。于是进行了向repo迁移的工作。结果你还别说,repo进行多个git库管理的时候还真是方便多了。于是现在的多模块管理变成了现在的样子:


> 以Cocoapods为核心工具,将每个子模块拆解成podspec。主工程中只有配置文件,没有代码,代码通过cocoapods的开发模式相对引入到的主工程中。每个子模块同时也都是一个git repo。通过Google Repo来管理多个git repo。


使用google repo不太推荐直接用google的原版,经验证[https://github.com/esrlabs/git-repo](https://github.com/esrlabs/git-repo)这个版本的repo进行了多项改进,更适合在没有girrit的情况下使用。因为我后台的源码管理用的是phabricator。所以需要一个直接push的repo工具。


到这个时候基本的多模块管理的框架就算搭起来了。但是吧还是有一些小的问题需要演进一下。比如每次新建一个子模块都得手工的去创建git repo得各种配置。于是就写了一个简单的创建podspec的脚手架[Hive](https://github.com/yishuiliunian/.hive)。意思吗就是蚁巢。希望所有的模块就像一个蚁穴一样错落有致。


还有个问题就是每次新建了项目会后都得手工的去修改Core下面的Podfile文件。需要保持Podfile文件中的podspec与Libs和Bizs下面的子工程保持一直,这也是个啰嗦的事情。于是对Podfile进行了改进。其实Podfile就是一个Ruby文件,在里面可以直接写Ruby的代码:


~~~

require 'pathname'

REPO_ROOT_NAME = '.repo'


def FindRepoRoot(path)

  pwd = Pathname(path)

  if pwd.to_path == '.repo'

    return pwd

  end

  aimPath = nil

  pwd.entries.each { |x|

    if x.to_path == REPO_ROOT_NAME

      aimPath = pwd.join(x)

      break

    end

  }

  if aimPath != nil

    return aimPath

  else

    parentDir = pwd.dirname

    if parentDir.to_path == REPO_ROOT_NAME

      return nil

    end

    return FindRepoRoot(parentDir)

  end

end



REPO_ROOT = FindRepoRoot(Pathname.getwd).realpath



def FindAllSubPodspec(path)

  podspecs = []

  path.entries.each { |entry|

    if (entry.extname == '.podspec' || entry.extname=='.podspec.json')

        podspecs.push(path.join(entry))

    end

  }

  if podspecs.count != 0

    return podspecs

  else

    path.entries.each {  |entry|

      next if entry.to_path == '.'

      next if entry.to_path == '..'

      if File.directory?(path.join(entry))

        subpath = path.join(entry)

        ps = FindAllSubPodspec(subpath)

        podspecs = podspecs ps

      else

      end

    }

  end

  return podspecs

end


def LibPod(name)

  pod name , :path=>'../Libs/' name

end


def DebugLibPod(name)

  pod name , :path=>'../Bizs/' name, :configurations => ['Debug']

end


def BizPod(name)

  pod name , :path=>'../Bizs/' name

end

post_install do |installer|

  installer.pods_project.targets.each do |target|

    target.build_configurations.each do |config|

      config.build_settings['ENABLE_BITCODE'] = 'NO'

    end

  end

  require 'fileutils'

  require 'rake'

  Rake.sh ''' markdown2 Pods/Target\ Support\ Files/Pods-YaoHe/Pods-YaoHe-acknowledgements.markdown > Pods/Target\ Support\ Files/Pods-YaoHe/Pods-YaoHe-acknowledgements.html

 echo '<title>法律声明</title> <meta charset='utf-8' /> ' | cat -  Pods/Target\ Support\ Files/Pods-YaoHe/Pods-YaoHe-acknowledgements.html  > temp.html; mv temp.html Pods/Target\ Support\ Files/Pods-YaoHe/Pods-YaoHe-acknowledgements.html

              cp Pods/Target\ Support\ Files/Pods-YaoHe/Pods-YaoHe-acknowledgements.html YaoHe/Resources/OpenSource.html


           '''

end

DEBUG_PODS = [

  'YHMessageTest'

]


EXCLUDE_PODS = [

  'Mars'

]


target 'YaoHe' do

  podspecs = FindAllSubPodspec(REPO_ROOT.dirname)


  puts('全部本地模块 #{podspecs}')

  podspecs.each { |itor|

    name = File.basename(itor,'.*')

    path = itor.relative_path_from(Pathname.pwd).dirname

    puts('pod name #{name} path #{path}')

    if not  name.include? 'TestCase'

      if DEBUG_PODS.include? name

        pod(name, :path=>path, :configurations => ['Debug'])

      elsif EXCLUDE_PODS.include? name

        puts 'Ignore Pod #{name}'

      else

        pod(name, :path=>path)

      end

    end


  }


  ## 引用的第三方库

  pod 'ChameleonFramework'

  .....

end


target 'YaoHeTests' do

  podspecs = FindAllSubPodspec(REPO_ROOT.dirname)


  puts('对测试模块进行 pod 注入....')

  podspecs.each { |itor|

    name = File.basename(itor,'.*')

    path = itor.relative_path_from(Pathname.pwd).dirname

    puts('pod name #{name} path #{path}')

    if name.include? 'TestCase'

      if DEBUG_PODS.include? name

        pod(name, :path=>path, :configurations => ['Debug'])

      else

        pod(name, :path=>path)

      end

    end

  }


  pod 'JXAsyncTest'

end

~~~


通过查找google repo的根目录,确定项目根目录,然后遍历每个目录找到所有的子模块。这样就可以一个函数将podspecs加进Podfile里面。经过这样改造之后,增加子模块时就不用关心Podfile的问题了。


这样一个基本顺手的模块管理方式就算基本上完工。这套东西其实也适合大团队使用,但是我一个人用起来也是蛮爽的。后面Libs下面的东西都在陆陆续续的开源出来。虽然不是大的作品,也算是自己这一年来的折腾的收获。


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
CocoaPods的介绍、安装、使用和原理
CocoaPods的安装使用和常见问题
CocoaPods实践与理解
查找名称为.git 并路径不包含.repo的记录
iOS组件化实践(基于CocoaPods)
Android 4.4 全套源码及子模块源码的下载方法
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服