如何在 Swift 中使用 CocoaPods
本文介紹如何在 Swift 項(xiàng)目中使用 CocoaPods 。如果你已經(jīng)精通 Bridging Header 的方法,請(qǐng)直接跳到 “擴(kuò)展 CocoaPods” 一節(jié)。
什么是 CocoaPods
CocoaPods is the dependency manager for Objective-C projects. It has thousands of libraries and can help you scale your projects elegantly.
從介紹看,它是主要給 Objective-C 項(xiàng)目用的,但是我們可以很容易地混合 Objective-C 和 Swift 到同個(gè)項(xiàng)目,從而利用大量的 CocoaPods 庫(kù)和 Swift 漂亮舒服的語(yǔ)法。
作為 iOS 開發(fā)新手,一定是要緊跟前人腳步,學(xué)習(xí)使用 CocoaPods 。
基礎(chǔ)用法
這里簡(jiǎn)單略過(guò),請(qǐng)參考其他無(wú)數(shù)的文章。
安裝
系統(tǒng)默認(rèn)安裝,可以參考其他教程 。在命令行下執(zhí)行。
- sudo gem install cocoapods
我的環(huán)境是 HomeBrew
- # 添加 taobao Mirror 不然被墻掉沒(méi)辦法下載
- gem sources -a http://ruby.taobao.org/
- # 安裝
- gem install cocoapods
- # 更新命令
- rbenv rehash
- # 執(zhí)行
- pod
- # 此時(shí)一般會(huì)下載官方的所有 PodSpec 庫(kù),也可以用 pod setup 初始化環(huán)境
本文不打算在安裝部分耗費(fèi)太多時(shí)間。希望看到這里保證你的命令行下有可用的 pod 命令。
使用
假設(shè)我們已經(jīng)有個(gè)項(xiàng)目,叫 ProjName ,需要使用一些注明的 CocoaPods 庫(kù),比如 AFNetworking3.
首先,命令行 cd 到我們的項(xiàng)目目錄,一般 ls 命令會(huì)看到如下幾個(gè)文件夾
- ProjName
- ProjName.xcodeproj
- ProjNameTests
贊,就是這里,創(chuàng)建一個(gè) Podfile 文本文件,寫入如下內(nèi)容
- platform :ios, "8.0"
- pod "AFNetworking", "~> 2.0"
一般這么簡(jiǎn)單的文件都是直接 nano 寫。 :)
直接創(chuàng)建 Podfile , CocoaPods 會(huì)創(chuàng)建一個(gè)項(xiàng)目同名的 WorkSpace ,然后添加一個(gè)叫 Pods 的項(xiàng)目,這個(gè)項(xiàng)目編譯結(jié)果是一個(gè)叫 libPods.a的鏈接庫(kù), 它會(huì)添加到我們之前的 ProjName 項(xiàng)目中作為編譯依賴。
當(dāng)然,通過(guò)命令行執(zhí)行 pod init 也可以自動(dòng)創(chuàng)建 Podfile,而且可以自動(dòng)分析當(dāng)前項(xiàng)目的 target ,相對(duì)來(lái)說(shuō)更好,也更優(yōu)雅。具體請(qǐng)參考官方手冊(cè)。這樣的好處是更細(xì)致,還可以區(qū)分多個(gè)子項(xiàng)目子 target 。原理大同小異。
然后接下來(lái),命令行執(zhí)行 open ProjName.xcworkspace,注意這個(gè)可不是 .xcodeproj,這個(gè)是 CocoaPods 為我們創(chuàng)建的一個(gè) WorkSpace ,包含我們之前的項(xiàng)目,和 Pods 依賴。
開始編碼過(guò)程。直接在代碼里調(diào)用,比如寫在某個(gè)按鈕的 @IBAction 里:
- let manager = AFHTTPRequestOperationManager()
- let url = "http://api.openweathermap.org/data/2.5/weather"
- println(url)
- let params = ["lat": 39.26, "lon": 41.03, "cnt":0]
- println(params)
- manager.GET(url,
- parameters: params,
- success: { (operation: AFHTTPRequestOperation!,
- responseObject: AnyObject!) in
- println("JSON: " + responseObject.description!)
- },
- failure: { (operation: AFHTTPRequestOperation!,
- error: NSError!) in
- println("Error: " + error.localizedDescription)
- })
這里直接抄了 JakeLin 的 SwiftWeather 代碼4,就一小段,希望他不會(huì)打我。
Swift 坑爹了
看起來(lái)貌似我們已經(jīng)可以在 Swift 中使用 AFNetworking 了。結(jié)果剛寫幾句代碼一堆類和變量找不到定義,而且坑爹的是很多時(shí)候我們只能靠猜測(cè),判斷這些 Objective-C 的定義轉(zhuǎn)換成 Swift 定義是什么樣子,用起來(lái)就是完全靠蒙!
這不科學(xué)!
這都三禮拜了,所以大家都摸索出了調(diào)用的方法,那就是按照和 Objective-C 代碼混編的例子,添加 Bridging Header !
繼續(xù)
之前簡(jiǎn)單介紹過(guò)和 Objective-C 交互的內(nèi)容5,大家可以去圍觀。
一般說(shuō)來(lái),你在 Swift 項(xiàng)目新建 Objective-C 類的時(shí)候,直接彈出是否創(chuàng)建 Bridge Header 的窗口,點(diǎn) YES 就是了,這時(shí)候一般多出來(lái)個(gè) ProjectName-Bridging-Header.h 。然后刪掉這個(gè)類, Bridging Header 頭文件還在。
在這個(gè) Bridging Header 文件里寫入要導(dǎo)入的 CocoaPods 庫(kù),就可以在 Swift 中使用了。
- #import <AFNetworking/AFNetworking.h>
如果沒(méi)有自動(dòng)創(chuàng)建頭文件的話,這個(gè)配置在項(xiàng)目的 Build Settings 中的 Swift Compiler – Code Generation 子項(xiàng)里。
創(chuàng)建一個(gè)頭文件,指定為 Bridging Header 也可以。
然后編譯,成功執(zhí)行!
這就完事了?
實(shí)際上,前兩天剛寫一篇 Swift 的模塊系統(tǒng) , 把任意 Objective-C 庫(kù)當(dāng)做 Swift Module 是可行的。當(dāng)時(shí)就覺(jué)得這個(gè)東西應(yīng)該是可能完全進(jìn)入 CocoaPods 的,但是在官方 repo 找了下發(fā)現(xiàn),以前有人提過(guò)增加 module.map 支持,結(jié)果 CocoaPods 的人認(rèn)為這個(gè)是 llvm 內(nèi)部特性, issue 被關(guān)閉了。#2216 最近又被提起,我在后面提了下 Swift 支持,希望官方靠譜。
所以下面的內(nèi)容,就是,我們是否可以在 CocoaPods 上加入 module.map 支持,然后直接在 Swift 中 import ModuleName ?
擴(kuò)展 CocoaPods
考慮了多種方式,***選擇了 Hook 的方式。如果 Ruby 技術(shù)足夠好,或許可以直接寫個(gè)插件?;蛘咧苯痈墓俜酱a給官方提交。但是實(shí)在能力有限。相關(guān)的 module.map 語(yǔ)法參考 llvm 官方手冊(cè) Modules – Clang 3.5 documentation。用了最簡(jiǎn)單的功能。也許遇到復(fù)雜的 PodSpec 就不起作用了,但是原理如此,相信小伙伴們已經(jīng)知道怎么做了。
目前我的 Podfile 大概是這個(gè)樣子:
- platform :ios, "8.0"
- pod "AFNetworking", "~> 2.0"
- pod "Baidu-Maps-iOS-SDK", "~> 2.0"
- post_install do |installer|
- File.open("#{installer.sandbox_root}/Headers/module.map", 'w') do |fp|
- installer.pods.each do |pod|
- normalized_pod_name = pod.name.gsub('-', '')
- fp.write <<EOF
- module #{normalized_pod_name} [system] {
- umbrella "#{pod.name}"
- export *
- }
- EOF
- puts "Generating Swift Module #{normalized_pod_name.green} for #{pod} OK!"
- end
- end
- end
post_install
是 Podfile
的一種 hook 機(jī)制,可以用來(lái)加入自定義操作。我在這里的寫的邏輯就是,針對(duì)所有的 Pod 生成一個(gè) module.map
文件。 位于 Pods/Headers/
,這個(gè)目錄被 CocoaPods 自動(dòng)設(shè)置為項(xiàng)目的 Header Search Path 所以不需要額外處理。默認(rèn)我們的 Swift 文件就找得到。
其中 normalized_pod_name
用于處理百度地圖 API SDK 這一類名字帶減號(hào)的庫(kù),因?yàn)樗麄儾荒茏鳛?Module Name ,實(shí)際上或許有更好的方法來(lái)處理。
實(shí)際效果
實(shí)測(cè)發(fā)現(xiàn)完全沒(méi)有問(wèn)題,直接 import AFNetworking
或者 import BaiduMapsiOSSDK
都可以。
而且很不錯(cuò)的一點(diǎn)是,按住 Command 鍵,然后鼠標(biāo)點(diǎn)擊模塊名、類名等,會(huì)跳轉(zhuǎn)到 Swift 定義。
坑
遇到提示 .pcm
文件 outdate 的情況下需要你刪除 $HOME/Library/Developer/Xcode/DerivedData/ModuleCache
目錄,這個(gè)目錄保存的是預(yù)編譯模塊,類似于預(yù)編譯頭文件。
目前 Swift 還是有很多 BUG 的,調(diào)用 NSObject
也許會(huì)讓編譯器直接 segment fault ,不帶任何出錯(cuò)信息。很傷情。此時(shí)請(qǐng)***時(shí)間檢查語(yǔ)法是否有詭異,其次將所有用到字符串或者 Optional
的地方都額外用變量處理,避免用字面常量。(個(gè)人經(jīng)驗(yàn))
如果多次調(diào)用 pod install
并在其中修改過(guò) Podfile
,那么有可能你的項(xiàng)目依賴會(huì)亂掉,多了不存在的 .a
文件到依賴或者多次包含。手工在項(xiàng)目樹和項(xiàng)目選項(xiàng)里刪除就可以了。此類編譯錯(cuò)誤都是鏈接錯(cuò)誤。
總結(jié)
本文提出了一種 Bridging Header 之外的使用 CocoaPods 庫(kù)的方法。利用有限的 Ruby 知識(shí)寫了個(gè) Hook 。目前測(cè)試 OK 。
參考
- CocoaPods Offical Site CocoaPods 官網(wǎng)↩
- CocoaPods – CocoaChina CocoaChina 對(duì) CocoaPods 的介紹↩
- AFNetworking – Github↩
- SwiftWeather↩
- Swift and ObjectiveC Interop (Swift 與 Objective-C 之間的交互)↩