cocoaPods的学习和使用

2016-03-27 | 阅读

CocoaPods是iOS最常用最有名的类库管理工具.

CocoaPods的简单使用

安装方式异常简单 , Mac 下都自带 ruby,使用 ruby 的 gem 命令即可下载安装:

$ sudo gem install cocoapods
$ pod setup

如果你的 gem 太老,可能也会有问题,可以尝试用如下命令升级 gem:

sudo gem update --system

另外,ruby 的软件源 https://rubygems.org 因为使用的是亚马逊的云服务,所以被墙了,需要更新一下 ruby 的源,使用如下代码将官方的 ruby 源替换成国内淘宝的源:

gem sources --remove https://rubygems.org/
gem sources -a https://ruby.taobao.org/
gem sources -l

使用 CocoaPods,使用时需要在项目目录新建一个名为 Podfile 的文件,以如下格式,将依赖的库名字依次列在文件中即可

platform :ios
pod 'JSONKit',       '~> 1.4'
pod 'Reachability',  '~> 3.0.0'
pod 'ASIHTTPRequest'
pod 'RegexKitLite'

关于详细的podfile的编写,也可以在官方文档中进行查看.然后你将编辑好的 Podfile 文件放到你的项目根目录中,然后在该目录下执行如下命令即可:

pod install

现在,你的所有第三方库都已经下载完成并且设置好了编译参数和依赖,使用 CocoaPods 生成的 .xcworkspace 文件来打开工程,而不是以前的 .xcodeproj 文件。使用install 会优先安装Podfile.lock文件中的内容,而不会主动更新Podfile.lock文件.而执行pod update方法会主动获取最新的库,并更新Podfile.lock文件.

pod的所有命令有:

+ cache      Manipulate the CocoaPods cache
+ init       Generate a Podfile for the current directory.
+ install    Install project dependencies to Podfile.lock versions
+ ipc        Inter-process communication
+ lib        Develop pods
+ list       List pods
+ outdated   Show outdated project dependencies
+ plugins    Show available CocoaPods plugins
+ repo       Manage spec-repositories
+ search     Search for pods.
+ setup      Setup the CocoaPods environment
+ spec       Manage pod specs
+ trunk      Interact with the CocoaPods API (e.g. publishing new specs)
+ try        Try a Pod!
+ update     Update outdated project dependencies and create new Podfile.lock

私有repo的搭建

默认所有的Podspec文件都放在了https://github.com/CocoaPods/Specs地址上,所以第一次pod setup时,耗费了许多时间. 对于公司级别的开发,需要搭建许多私有的类库,这就需要我们创建一个私有的repo.

  1. 创建一个私有的库 :

     $ cd /opt/git
     $ mkdir Specs.git
     $ cd Specs.git
     $ git init --bare 
     // 要使用git传输协议,需要创建git-export-daemon-ok文件,然后
     touch git-daemon-export-ok  
    
  2. 在设置中添加这个远程库.远程库名称定义为 abc-specs

     $ pod repo add abc-specs git@github:artsy/Specs.git
     $ cd ~/.cocoapods/repos/artsy-specs
     $ pod repo lint .
    
  3. 添加知己的私有库

     pod repo push abc-specs hello-world.podspec --verbose --allow-warnings
     // 任何一丁点的警告都会导致提交失败,所以要允许所有警告.
    
     // 企图删除master是没有用的.
    
  4. 最后在使用私有库时,在Podfile上写上这句:

     source 'git://10.75.94.133/Specs.git'	
     source 'https://github.com/CocoaPods/Specs.git'
    

pods的repo仓库的目录结构是:

├── Specs
	└── [SPEC_NAME]
   		└── [VERSION]
       	 		└── [SPEC_NAME].podspec

最近使用CocoaPods来添加第三方类库,无论是执行pod install还是pod update都卡在了Analyzing dependencies不动,原因在于当执行以上两个命令的时候会升级CocoaPods的spec仓库,调用pod repo update方法,而如果你没有设置master repo,会强行给你设置,这里说master repo是指公有的pod 库,即使你在本地自己建一个master的repo,它也会给你下载一个master-1的公开库下来.

删除spec的某个版本:

pod repo xxx delete PODName VERSION

私有库podspec文件的编写

官方文档对于podspec文件的编写进行了比较详细的说明, 这里我对我觉得比较常用的几项进行一下简单的介绍.

// 名称,必须与xxx.podspec文件同名
spec.name = 'AFNetworking'
// 版本,一般设置为git的tag版本
spec.version = '0.0.1'
// 作者,随便写写
spec.author = 'Darth Vader'
// 源文件,我们使用git的管理库的源文件,用当前版本号作为tag
spec.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git',
            :tag => spec.version }
// 简单介绍
spec.summary = 'Computes the meaning of life.'
// 安装前准备的命令脚本
spec.prepare_command = <<-CMD
                    sed -i 's/MyNameSpacedHeader/Header/g' ./**/*.h
                    sed -i 's/MyNameOtherSpacedHeader/OtherHeader/g' ./**/*.h
               CMD
// 要求版本
spec.ios.deployment_target = '8.0'
// 依赖 版本简单介绍一下,如果不写版本,表示使用最新版本, ~>表示大于多少版本 '1.0'表示使用1.0版本
spec.dependency 'AFNetworking', '~> 1.0'
// 是否使用arc,默认使用
spec.requires_arc = true
// 也可以这样来指定文件使用ARC,但是不使用ARC的文件,就必须用`-fno-objc-arc` compiler flag来设置了
spec.requires_arc = ['Classes/*ARC.m', 'Classes/ARC.mm']
// 依赖的framework, Foundation和 UIKit可以忽略
spec.frameworks = 'QuartzCore', 'CoreData'
// 系统库的连接,这里是 libxml2.tbd的东西.
spec.libraries = 'xml2', 'z'
// compiler flag
spec.compiler_flags = '-DOS_OBJECT_USE_OBJC=0', '-Wno-format'
// 源文件
spec.source_files = 'Classes/**/*.{h,m}', 'More_Classes/**/*.{h,m}'
// 公开头文件,如果不设置,那所有的头文件都是公开的.
spec.public_header_files = 'Headers/Public/*.h'
// 如果要导入一个第三方或者自己的framework文件,需要使用--use-libraries
spec.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.framework'
// 需要导入的.a库.
spec.vendored_libraries = 'libProj4.a', 'libJavaScriptCore.a'
// 需要导入的资源文件,注意,不能嵌套文件夹,简单来说,Sounds/*下面所有的文件都被导入到Pod中,包括文件夹,如Sound下有个文件夹/hello/123.png,也会被导入,但是最后的123.png文件却不会被正确加载到项目中.如果要正常文件夹形式,只能通过 subspec即子库的方式来实现.
spec.resources = ['Images/*.png', 'Sounds/*']
// 子库,只有子库才能创建文件夹....
subspec 'Pinboard' do |sp|
  sp.source_files = 'Classes/Pinboard'
end
// 指定一个默认子库,这个子库却是加载全部...而如果不设置默认子库,也会默认加载全部
s.default_subspec = 'All'

编写好之后,上传podspec文件

在git仓库确定打好tag,修改好正确podspec文件后,就可以提交了.首先,现在本地连接上可以修改版本的pod repo.

pod repo add abc-specs xxx@10.75.94.133:/usr/local/cocoaPodsRepo/Specs.git

关于pod repo常用的命令有 :

// 查看当前的repo库列表 ,默认会有一个master,是连接到github上的公有库
pod repo list
// 更新 ,默认 pod install update时,都会进行pod repo update操作来加载主干库,所以使用参数--no-repo-update.
pod repo update
// 删除一个
pod repo remove abc-specs

然后 提交 :

pod repo push abc-specs MUCardScanner.podspec --verbose  --allow-warnings

verbose打印出过程,--allow-warnings允许警告,默认情况下,任何警告都会导致提交失败. 如果使用了vendored_frameworks,还要加上参数--use-libraries. 由于pod提交时比较麻烦,必须先构建一遍,且不能只提供指定架构版本,所以我们常常直接操作pod仓库,而不是通过命令pod repo push去提交。

需要注意的地方:

  • podspec中依赖framework,使用vendored_libraryvendored_frameworks属性时,提交podspec时,会报错:

      The 'Pods' target has transitive dependencies that include static binaries:xxx.framework`
    

    解决方法是,pod repo push时添加参数--use-libraries.

  • podspec中设置other link flag :

      s.xconfig = {
              'OTHER_LDFLAGS' => '$(inherited) -lstdc++',
          }
    
  • podspec设置资源文件,关于资源文件的话,资源文件只能传递一层,所以不应该使用嵌套的文件夹来存放资源文件,事实上,如源代码一样,想要分包装代码或者资源文件,就必须建立subspec了.可以使用imagesets来保存图片.
  • 本地的缓存路径在 ~/Library/Caches/CocoaPods/Pods
  • 删除库时,执行完了update后,但是项目中设置的other link flags还在,所以需要再执行一次pod install来删除配置.
  • 所有的静态库都要以lib开头,如使用vendored_library加载libABC.a时,会在other link flag上设置-l "ABC"
  • pod repo是有缓存的,如果你成功上传一个podspec,但是版本没有改变的话,然后在本地通过 pod update想要下载这一份最新的库时,得到的只是本地的缓存而已.所以如果不改动版本,必须删除本地的缓存.
  • 关于ARC的设置,可以考虑使用subspec来设置子库一部分支持ARC,一部分不支持ARC.
  • 会出现ArgumentError - invalid byte sequence in US-ASCII编码集错误,需要因为编译机器不是UTF-8编码的,进行编码声明export LC_ALL=en_US.UTF-8

在Podfile中使用hook

$mainTarget = 'Podtest1'
def func1
  i=0
  while i<=10
    puts "func1 at: #{Time.now}"
    sleep(1)
    i=i+1
    debugSetting = "Pods/Target Support Files/Pods-#{$mainTarget}/Pods-#{$mainTarget}.debug.xcconfig" 
    releaseSetting = "Pods/Target Support Files/Pods-#{$mainTarget}/Pods-#{$mainTarget}.release.xcconfig" 
  	if File::exist?(debugSetting) && File::exist?(releaseSetting)
    	puts "当前文件已存在,删除可恶的-ObjC"
        fileStr = File.open(debugSetting).read
        puts "当前内容为 :\n #{fileStr}"
        fileStr["-ObjC"] = ""
        puts "删除 -ObjC后 :\n #{fileStr}"
        file = File.open(debugSetting,"w+")
		file.puts fileStr
		file.close

		puts "当前文件已存在,删除可恶的-ObjC"
        fileStr = File.open(releaseSetting).read
        puts "当前内容为 :\n #{fileStr}"
        fileStr["-ObjC"] = ""
        puts "删除 -ObjC后 :\n #{fileStr}"
        file = File.open(releaseSetting,"w+")
		file.puts fileStr
		file.close
        i = 11;
  	end
  end
end

post_install do |installer|
  t1 = Thread.new{ func1()}
  t1.join
end

类似这样,顺便学习了一下ruby. 但是 尝试删除 -ObjC是彻底的失败了.

首先,cocoaPods在使用-ObjC原因,因为CocoaPods的机制是 Xcodeworkspace动态联编,workspace中有两个项目一个是本身项目,一个是pod项目,pod项目中包含了所有的依赖库,而两个项目的联系是通过这个workspace直接连在一起的,所有的依赖库是以静态库(使用use_framework会转为动态库)的形式被加载到项目中的.所以 依赖库中经常会有这种 非直接显示调用的类和代码,这些代码在静态库中是不会被加载到项目中的,所以必须要加上一个-ObjC的flag.

对于静态库传递

静态库传递,Static Transitive Dependencies的问题可以参考Github上的Issue .

来介绍一下这个问题,问题在于静态库依赖的传递,A库依赖B库,而B库中含有一个通过vendored_libraries加载的静态库.aframewrok.这种情况,在Podfile中不使用use_frameworks!时,一切表现正常.但是在swift项目中必须要使用这个use_frameworks!标记时,就会发生异常,爆出这个错误:

[!] The 'Pods-XXXDemo' target has transitive dependencies that include static binaries: (/Users/lxm/Documents/developer/WebviewDemo/Pods/.../xxx.a)

其原因是,在不使用use_frameworks!标记时,嵌套的第三方库直接通过-l连接到项目中,而A库只编译自己的部分,所以所有的互相传递的依赖的静态库都能最终被导入.但是在使用use_frameworks!,打包的framework可能会包含vendored_libraries库中的内容,所以这里就有一个符号冲突的问题了.而CocoaPods对于这种问题,统一通过报错来拒绝这种情况.

解决方法:

依旧以A库依赖B库为例,B库中有一个静态库libB.a :

在A库中修改.podspec :

s.pod_target_xcconfig = {
    'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PODS_ROOT)/Crashlytics',
    'OTHER_LDFLAGS'          => '$(inherited) -undefined dynamic_lookup'
}

然后在Podfile中添加hook

pre_install do |installer|
    # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
    def installer.verify_no_static_framework_transitive_dependencies; end
end

在项目中使用和管理CocoaPods

  • 首先,代码管理上,只上传Podfile,来控制库的版本,不上传Pods文件夹和.xcworkspace.
  • 建立私有repo库,将所有的第三方库纳入私有库中管理,这样编译时,就可以不用连接外网编译了.
  • 对于改写第三方库代码,提交新的版本,在版本号上加上标记-xxx,以标识是我们自己的版本.

CocoaPods 在主干上发布pods

CocoaPods Trunk 是一个基于授权的CocoaPodsAPI服务。 要在主干上发布或者更新库,需要注册,并获得一个 验证了的主干Session在当前的设备上。0.33版本开始支持。

以邮箱注册账号,同时会根据当前设备,生成一个会话。

pod trunk register orta@cocoapods.org 'Orta Therox' --description='macbook air'

后面的`description`是附加的,给用户自己看的当前session描述,因为会话是绑定设备的,所以描述一下设备,还是有必要的。

然后去邮箱验证,验证后,可以通过命令 pod trunk me来查看账号所有Session

然后通过命令pod trunk push来发布pod。