iOS技术积累

不管生活有多不容易,都要守住自己的那一份优雅。

iOS 从入门到中级

最近有个同学说比较迷茫说,自己做iOS也快三年了,为什么技术水平一点都没有长进。

我回答的是 个中原因你自己不清楚吗?他说是打游戏太多了,bulabula~中间谈天说地吹牛逼的经过就省略了。

最后聊到他其实也想努力学习,但是实在是不知道学习什么,好让自己更上一步。 想来确实是这样的,做为一个纯小白的时候觉得自己好像什么都能做,但是会点东西之后发现做东西的时候什么都不会,问大牛的时候,大牛又总是不屑回答。于是非常容易进入一个无法提升技术水平的怪圈。

于是我就把我这几年来看过的书按照我觉得合适的顺序做了一个学习路线图。个人认为如果能踏踏实实学完上面的知识, 找个iOS中级工程师的工作还是很随意的。

当然因为个人能力有限,推荐的书籍也不见得非常好,也不一定非常全,但是尝试着学习点知识总不至于吃什么亏。

最重要的是个人觉得如果你能坚持看完这上面60%的书籍,那么随着视野的开阔,你也拥有了找到适合自己学习路线的能力。

iOS程序员救赎之路.png

阅读更多

组件化-二进制方案

为什么要做二进制

随着APP业务规模越来越大,代码量的不断增多,再加上团队迭代节奏变快,开发的痛点越来越明显—编译耗时较长。目前我司代码量约40w行左右, 业务开发人员每次从git仓库拉代码都会让Xcode重新编译.不同开发机性能也不一,快的可以8min编译完,慢的则达到20min,极大的影响了开发效率。
另外一种需求是我们的代码对于不同的团队有不同的权限管理。我可能不希望直接发布我的源码,而是通过二进制和文档的方式提供给业务方。

二进制需求

既然我们想做二进制,那么我们要考虑到我们如何做,如何来衡量做出来的二进制方案的成效。
参考网上二进制方案和自己公司内业务开发的痛点我们列了如下的需求。

  1. 我需要平滑的过度到二进制方案,不能对业务团队有大的影响

  2. 对于二进制库,编译虽然快了,但是出现错误无法调试,我需要更快捷的切换为源码形式

  3. 可以复用原来的组件仓库,不希望把二进制到处复制

  4. 二进制的生成有更方便的流程

  5. 我希望我的调用方不需要有奇怪的调用方式,如 '2.2.1 Binary'

  6. 虽然Podfile是ruby代码但是很多业务方的同学不懂,不希望出现ruby逻辑,增大接入难度

  7. 照例使用旧的pod install或者pod update命令,不希望有其他别的操作。

  8. 组件提供者不希望维护多份Podspec文件

     结合前面几篇讲解组件化-动态库的文章。我又加了一条
  9. 二进制是静态库,最好是静态framework。

二进制实施方案

公司的库无非就2种:

  1. 第三方库
  2. 自己开发的私有库
    下面分别讲解

私有库二进制方案

如何制作静态库

首先我们肯定是有源码才有的二进制。前面的需求也提到了组件维护者需要很容易的得到二进制库。首先想到的就是写脚本,但是脚本肯定依赖于模板工程,然后配置源文件,配置资源文件,配置依赖。又是工作量~~。所幸,CocoaPods已经有一个插件cocoapods-packager帮我们提供这个功能。
安装很简单执行下面命令

 gem install cocoapods-packager

那么我们前面提到的配置源文件,配置依赖,配置资源文件,其实有一个东西 我们已经做过了。那就是我们对外发布的PodSpec文件。
那么打包一个库也很简单

➜  0.0.4 git:(master) ✗ pod package --help
Usage:

    $ pod package NAME [SOURCE]

      Package a podspec into a static library.

Options:

    --force                                                         Overwrite existing
                                                                    files.
    --no-mangle                                                     Do not mangle
                                                                    symbols of
                                                                    depedendant Pods. 
    --embedded                                                      Generate embedded
                                                                    frameworks.
    --library                                                       Generate static
                                                                    libraries.
    --dynamic                                                       Generate dynamic
                                                                    framework.
    --bundle-identifier                                             Bundle identifier
                                                                    for dynamic
                                                                    framework
    --exclude-deps                                                  Exclude symbols
                                                                    from dependencies.
    --configuration                                                 Build the
                                                                    specified
                                                                    configuration
                                                                    (e.g. Debug).
                                                                    Defaults to
                                                                    Release
    --subspecs                                                      Only include the
                                                                    given subspecs
    --spec-sources=private,https://github.com/CocoaPods/Specs.git   The sources to
                                                                    pull dependant
                                                                    pods from
                                                                    (defaults to
                                                                    https://github.com/CocoaPods/Specs.git)

以下是一个简单的demo

pod package LJStability.podspec --embedded 

接下来将这个二进制文件放在我们的源码仓库即可

发布我们的二进制

我们解决了如何快速制作二进制库,那么如何发布呢?
网上很多方案是在podspec写判断条件,然后通过特殊的install/update方式去使用。在我们这边的实践情况来看,这套方案有几个弊端。

  1. 使用这些库让业务方改变了使用pod的方式
  2. 切换源码还是二进制是全局的,我无法做到只切换某个组件为源码形式。
  3. CocoaPods自身会给我们的install/update做缓存,本来是好事,但是现在变成了负担,我们要频繁的清理缓存,造成了下次install/update缓慢。

那么我们能不能一个PodSpec发布多个子pod呢,当然是支持的,CocoaPods官方文档介绍了subspec这个东西。这个subspec原本是做更细程度的模块划分,并且这些模块间是不能有互相依赖的(可以单向),换个想法既然都互相依赖了那就说明这些模块是无法做到更详细的拆解的就不适合拆分做subspec。关于Subspec做的比较好的有2个库 AFNetworking.podspecSDWebImage.podspec
那么我们可以指定做2个subspec一个叫做framework一个叫做source,指定默认的为framework即可,那么我在使用的时候有下面两种引入方法

pod 'Test' # 这会默认导入二进制库
pod 'Test/source' 这会导入源码

下面是podspec书写

Pod::Spec.new do |s|
s.name             = "LJStability"
s.version          = "0.0.5"
s.summary          = "链家网防Crash组件"
s.description      = "链家网防Crash组件支持7种Crash防护 \
1. unrecoginzed Selector Crash \
2. KVO Crash \
3. Container Crash \
4. NSNotification Crash \
5. NSNull Crash \ 
6. NSTimer Crash \
7. 野指针 Crash  "


s.license          = {:type => 'MIT', :file => 'LICENSE'}
s.homepage         = 'xx.git'
s.author           = { "author" => "xx@lianjia.com" }
s.source           = { :git => "http://git.xx.git", :commit => "" }

s.platform              = :ios, '7.0'
s.ios.deployment_target = '7.0'


  s.default_subspec = 'framework'
  s.subspec 'source' do |source|
    source.source_files = 'LJStability/LJStability/Classes/**/*'

  end

  s.subspec 'framework' do |framework|

    framework.ios.vendored_frameworks = 'LJStability/Pod/*.framework'
  end




# dependencys
s.dependency 'Crashlytics'
s.dependency 'JRSwizzle'


end



通过上面的方案我们解决了前面所说的三个问题。业务方正常使用install/update即可,我可以动态切换部分组件到源码形式,我也不用总是频繁的清理pod的缓存。

如何lint

我们为了保证将要发布的podspec是正确的,会有lint(校验)这个步骤

➜  ~ pod spec lint --help
Usage:

    $ pod spec lint [NAME.podspec|DIRECTORY|http://PATH/NAME.podspec ...]

      Validates `NAME.podspec`. If a `DIRECTORY` is provided, it validates the podspec
      files found, including subfolders. In case the argument is omitted, it defaults
      to the current working dir.

Options:

    --quick                                           Lint skips checks that would
                                                      require to download and build
                                                      the spec
    --allow-warnings                                  Lint validates even if warnings
                                                      are present
    --subspec=NAME                                    Lint validates only the given
                                                      subspec
    --no-subspecs                                     Lint skips validation of
                                                      subspecs
    --no-clean                                        Lint leaves the build directory
                                                      intact for inspection
    --fail-fast                                       Lint stops on the first failing
                                                      platform or subspec
    --use-libraries                                   Lint uses static libraries to
                                                      install the spec
    --sources=https://github.com/artsy/Specs,master   The sources from which to pull
                                                      dependent pods (defaults to
                                                      https://github.com/CocoaPods/Specs.git).
                                                      Multiple sources must be
                                                      comma-delimited.
    --private                                         Lint skips checks that apply
                                                      only to public specs
    --swift-version=VERSION                           The SWIFT_VERSION that should be
                                                      used to lint the spec. This
                                                      takes precedence over a
                                                      .swift-version file.
    --skip-import-validation                          Lint skips validating that the
                                                      pod can be imported
    --silent                                          Show nothing
    --verbose                                         Show more debugging information
    --no-ansi                                         Show output without ANSI codes
    --help   

之前我们校验一个podspec文件是否书写正确可能只是简单的加上文件名即可,不过有了subspec我们就要关心这些参数了 ,如果使用了上文说的工作方式,那么我们就要分别校验2个subspec了,并且由于我们制作的是静态库,需要加上--use-libraries 默认情况下使用的是use_framework!模式。

业务方更进一步的需求

本来到了这里,我们的教程基本结束了,但是业务方提了新需求,上文的截图中我们看到,我们发布的源码是没有文件夹组织结构的,业务方同学说,我能不能更好的组织下文件夹结构,这样方便我调试。
这里的解决办法其实也很简单,将subspec分为2大部分,1是private的subspec用于组织文件夹结构,2是public的subspec如 framework和source用于供调用方使用。
以下是一个例子

Pod::SPod::Spec.new do |s|
s.name             = "LJStability"
s.version          = "0.0.5"
s.summary          = "链家网防Crash组件"
s.description      = "链家网防Crash组件支持7种Crash防护 \
1. unrecoginzed Selector Crash \
2. KVO Crash \
3. Container Crash \
4. NSNotification Crash \
5. NSNull Crash \ 
6. NSTimer Crash \
7. 野指针 Crash  "


s.license          = {:type => 'MIT', :file => 'LICENSE'}
s.homepage         = 'xx.git'
s.author           = { "author" => "xx@lianjia.com" }
s.source           = { :git => "http://git.xx.git", :commit => "" }

s.platform              = :ios, '7.0'
s.ios.deployment_target = '7.0'



  s.default_subspec = 'framework'

#public
  s.subspec 'source' do |source|


    source.dependency 'LJStability/SDK'
    source.dependency 'LJStability/FoundationContainer'
    source.dependency 'LJStability/KVO'
    source.dependency 'LJStability/Notification'
    source.dependency 'LJStability/NSNull'
    source.dependency 'LJStability/NSTimer'
    source.dependency 'LJStability/SmartKit'
    source.dependency 'LJStability/DanglingPointerStability'
    source.dependency 'LJStability/Record'

  end

  s.subspec 'framework' do |framework|

    framework.ios.vendored_frameworks = 'Pod/*.framework'
  end


#  private
  s.subspec 'SDK' do |sdk|
    sdk.source_files = 'LJStability/Classes/*'
    sdk.requires_arc = true
  end
  s.subspec 'FoundationContainer' do |foundationContainer|
    foundationContainer.requires_arc = true
  end
  s.subspec 'KVO' do |kvo|

    kvo.requires_arc = true
  end
  s.subspec 'Notification' do |notification|
    notification.requires_arc = true
  end
  s.subspec 'NSNull' do |nSNull|

    nSNull.requires_arc = true
  end
  s.subspec 'NSTimer' do |nSTimer|

    nSTimer.requires_arc = true
  end
  s.subspec 'SmartKit' do |smartKit|

    smartKit.requires_arc = true
  end
  s.subspec 'DanglingPointerStability' do |danglingPointerStability|

  end
  s.subspec 'Record' do |record|
    record.requires_arc = true
  end





      # dependencys
      s.dependency 'Crashlytics'
      s.dependency 'JRSwizzle'


  end

上面的关于公司内部文件我删除了,以上部分只是一份参考。
调用方如图

后续优化

至此,我们的私有库组件二进制的方案已经演示完毕。
这里我要总结下我的看法 :
CocoaPods只是一个工具,我们在使用的时候要么把这个工具用的熟练到极致,要么我们修改这个工具创造出更适合的工具。
强烈建议学下ruby,自己去定制CocoaPods
不过我们上面的教程虽然给业务方解决了问题,但是其中还是有很多人工成本在里面的,人工编写podspec文件,人工打包静态库,人工lint podspec。这些都是重复性且没有技术含量的工作。
所以我们后续优化的点有以下:

  1. 根据规范自动的生成podspec文件
  2. 自动的打包静态库
  3. 更新podspec文件的多个subspec
  4. 自动校验podspec的文件是否正确

第三方库二进制方案

关于第三方库,由于podspec文件并不在我们控制,所以我们不能自己去发布并且修改podspec,网上有的方案是使用prepare_command。文后有参考链接这里不做讨论了。
但是可以预见的随着公司规模的扩大,我们最好的做法就是对于第三方代码和podspec文件完全做镜像。我们自己控制这些库,也就不存在第三方库一说了。

参考

  1. CocoaPods组件平滑二进制化解决方案 ​​​​
  2. Pod 预编译,减少不必要的生命浪费
  3. I have a pod, I have a carthage, En...
  4. 谈谈CocoaPods组件二进制化方案
阅读更多