讓自己的代碼支持Cocoapods導(dǎo)入

本文講述如何讓自己的代碼、.a、.framework支持cocoapods導(dǎo)入,變成類似于AFNetworking、SDWebImage那樣的第三方庫。代碼可以托管在SVN和Git服務(wù)器上,會在不同之處做出說明。是公有庫還是私有庫取決于你的代碼托管平臺設(shè)置的公開性。

初級階段

所謂“初級階段”,是說使用cocoapods提供的模板創(chuàng)建項目,然后將自己的代碼添加進去,做一些修改之后發(fā)布出去。這種方式相對簡單,結(jié)構(gòu)固定,但是靈活性不高。

下面進入正題~

1. 創(chuàng)建空項目

首先在SVN或者Git服務(wù)器上創(chuàng)建一個空項目,然后check out或者clone到本地。

2. 創(chuàng)建模板工程

打開終端,cd到工程所在目錄,輸入命令:pod lib create 工程名。Cocoapods會在終端詢問幾個問題,下面結(jié)合圖片解釋:

create lib.jpg

5個問題回答完之后,將會自動打開項目。你需要把自己的代碼放在Pods/Development Pods/目錄下。

3. 修改配置

在Xcode的Demo項目中的Podspec Metadata文件夾下,或者Finder中該項目的根目錄下找到一個后綴為podspec的跟項目名同名的文件,這個是讓自己的代碼支持Cocoapods導(dǎo)入的核心配置文件,下面對文件中各配置項的含義進行逐條說明:

Pod::Spec.new do |s|
  # 庫名
  s.name             = 'TestPoood'
  # 版本號
  s.version          = '0.1.0'
  # 對庫的類型、作用做一個簡短的描述
  s.summary          = 'A short description of TestPoood.'

  # 如果需要對庫進行較多的說明,可在此處進行描述
  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  # 庫工程目錄地址,這里會根據(jù)你在SVN或Git服務(wù)器上創(chuàng)建項目的地址自動生成
  s.homepage         = 'https://github.com/RavenKite/TestPoood'
  # 遵守的哪些許可協(xié)議,默認就行了
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  # 作者信息
  s.author           = { '作者名' => '郵箱' }

  # 庫的源路徑。SVN與Git稍有不同,下面分為兩行舉例。后面的`:tag => s.version.to_s`是庫的版本號,
  # `pod install`時會自動根據(jù)后面的tag版本號去工程目錄中尋找與版本號同名的源。
  s.source           = { :svn => 'http://192.168.101.1/svn/iOS/TestPoood/', :tag => s.version.to_s  }
  s.source           = { :git => 'https://github.com/RavenKite/TestPoood.git', :tag => s.version.to_s }

  # 最低兼容的iOS版本號
  s.ios.deployment_target = '8.0'
  # 庫中源碼的路徑,默認包含Classes下所有文件夾及所有的.h和.m文件
  # 注意:如果包含xib或storyboard,放在此目錄下是無效的,必須將其移動到Assets目錄下作為資源文件存在
  s.source_files = 'Classes/**/*.{h,m}'

  # 資源文件目錄,可以在此目錄下存放圖片、xib等資源,可以使用通配符或者{png,jpg,xib}這樣的方式來指定文件類型
  s.resource_bundles = {
    'TestPoood' => ['TestPoood/Assets/*.*']
  }

  s.public_header_files = 'Pod/Classes/**/*.h'
  # 依賴的系統(tǒng)庫(framework)。多個可以使用英文逗號隔開
  s.frameworks = 'UIKit', 'MapKit'
  # 依賴的系統(tǒng)dylib庫,依賴這種庫時不需要寫前面的"lib",如依賴"libz.tbd",只需要寫上'z'就行了
  s.ios.library = 'z','c++'

  # 依賴的第三方庫。如果依賴多個,不能使用依賴系統(tǒng)庫的方式用英文逗號隔開,必須要另起一行,后面也可以指定版本號
  s.dependency 'AFNetworking', '~> 2.3'
  s.dependency 'Masonry'

end
4. 代碼位置

配置文件修改好之后,將你的代碼放到Pods/Development Pods/TestPoood下,然后在終端執(zhí)行pod install后才能在Demo項目中調(diào)用。

5. 校驗

在本地的工作(配置、代碼)完成之后,提交SVN或Git服務(wù)器之前,最好先讓Cocoapods做一下校驗,校驗通過后再提交,以確保你的庫能夠被其他項目正常導(dǎo)入。
在終端執(zhí)行以下命令:

pod lib lint TestPoood.podspec

如果終端打印了TFFramework passed validation. 則說明校驗通過,否則校驗失敗。
如果校驗失敗,可以查看終端輸出的日志。
- NOTE是正常項;
- ERROR是錯誤項,需要根據(jù)后面的提示做出修改,否則無法通過校驗。錯誤情況可能五花八門,在此也無法窮舉,這里就考驗大家解決問題的能力啦(~ ̄  ̄)~;
- WARN是警告項,建議根據(jù)提示做出修改,也可以通過在校驗命令后拼接--allow-warnings忽略警告以完成校驗,代碼如下:

pod lib lint TestPoood.podspec --allow-warnings
6. 提交代碼服務(wù)器

在以上功能都實現(xiàn)完成后,提交到SVN或Git服務(wù)器,打上tag(tag名為版本號,如1.0.0,不要加其他中英文字符),GitHub可以Draft a new release。別的項目接入時跟引用其他第三方庫稍有不同,下面是引用示例。

在podfile文件中增加:

# SVN
pod 'TestPoood', :svn => 'http://192.168.101.1/svn/iOS/TestPoood/',:tag=>'0.1.0'
# Git
pod 'TestPoood', :git => 'https://github.com/RavenKite/TestPoood.git',:tag=>'0.1.0'

與引用常見的如AFNetworking不同的是(也可以跟引入AFN時一樣,需要將.podspec文件上傳到Cocoapods的服務(wù)器,后面的高級階段會講到),引用自己的庫需要在后面增加源地址,源地址就是.podspec文件s.source中的路徑,再之后的tag可根據(jù)需要使用指定的版本。

但是,開發(fā)狀態(tài)時,由于可能會頻繁的修改庫中的文件,而在別的項目中接入該庫時必須要指定一個版本號,這樣頻繁的修改文件后提交服務(wù)器然后打tag的方式過于繁瑣且沒必要,我們可以通過下面的方式,不指定版本號,而是直接引入庫的開發(fā)環(huán)境時的代碼,等到穩(wěn)定后再使用上面的方式引入。

# SVN直接引用trunk目錄下的源文件
pod 'TestPoood', :svn => 'http://192.168.101.1/svn/iOS/TestPoood/trunk'
# Git不指定后面的版本號,即會自動引用根目錄中的代碼,而不是tag中的
pod 'TestPoood', :git => 'https://github.com/RavenKite/TestPoood.git'
6. 修改和更新

如果版本號、庫的公開類、私有類、目錄結(jié)構(gòu)、資源文件等變更時,需修改.podspec中的配置然后提交SVN或Git,之后在引用該庫的工程中 pod update。
注意,podspec中的版本號要與Pods->TestPood中的版本號一致,且每次提交SVN或Git也應(yīng)該改變版本號(當(dāng)然,開發(fā)過程中可不必如此)。

7. 完成

至此,你已經(jīng)按照模板成功的制作出了支持Cocoapods導(dǎo)入的開源庫。
但是,如果你的庫中的代碼較多,或者你喜歡把不同的類按照其功能性等方式分散在不同文件夾中的話,你會發(fā)現(xiàn),制作出來的庫是沒有文件夾、完全扁平化的。然而,你會發(fā)現(xiàn)AFNetworking有文件夾的。如果你去它的GitHub地址查看AFNetworking.podspec文件的話,你會發(fā)現(xiàn)里面有s.subspec這個配置項,它的含義是創(chuàng)建一個子庫,具體使用方式會在下面的高級階段介紹,因為它對庫的結(jié)構(gòu)及代碼耦合性有很高的要求。

高級階段

如果你按照“初級階段”介紹的方法成功的制作出了一個簡單的庫的話,那么你肯定會發(fā)現(xiàn)這其中最重要的就是.podspec文件,尤其是它內(nèi)部的配置項,這才是決定了我們的代碼能否支持Cocoapods導(dǎo)入的關(guān)鍵。

并且你可能會發(fā)現(xiàn),“初級階段”所講的方式實際上是有很明顯的缺點的:它是按照模板來的,結(jié)構(gòu)過于固定而沒有靈活性,需要自己把代碼一點點的遷移到這個模板項目中來。而且這個項目結(jié)構(gòu)……我個人感覺有點奇怪。另外就是,我沒有提到怎么將.a或.framework添加進模板項目,因為我覺得采用這種模板的方式不適合制作閉源庫。

那么,在所謂的“高級階段”,就是要介紹怎樣靈活的創(chuàng)建和配置.podspec文件,怎樣讓它與項目結(jié)合起來,甚至可以在不影響現(xiàn)有項目的基礎(chǔ)上,將項目中任何一部分代碼制作成開源庫(閉源庫還是要對項目結(jié)構(gòu)做一些修改的),并支持Cocoapods導(dǎo)入。

好了,廢話說完了,現(xiàn)在開始!

這次,我們不再按照模板創(chuàng)建項目,而是先手動創(chuàng)建好項目之后(如果你打算使用自己現(xiàn)有的項目,則可以跳過第一步),再用終端命令創(chuàng)建.podspec文件。
我先把下面所講內(nèi)容用到的Demo地址貼出來,希望能給大家提供一些參考。

1. 首先,創(chuàng)建好項目,項目結(jié)構(gòu)如下圖所示。

其中,TFClasses是庫的類文件存放目錄,TFAssets是庫的資源文件存放目錄。
對下圖中包含.xcworkspace的項目結(jié)構(gòu)有疑問的童鞋,我安利下自己的另一篇文章。(~ ̄▽ ̄)~

TestFramework項目目錄結(jié)構(gòu)

2. 創(chuàng)建podspec

打開終端,cd到TestFramework項目的根目錄,即TestFramework.xcworkspace所在的目錄(使用自己項目的童鞋根據(jù)自己項目情況cd到根目錄);終端輸入以下命令pod spec create 庫名,回車。
創(chuàng)建成功后,打開TFFramework.podspec,你會發(fā)現(xiàn)里面有一大堆東西,不要擔(dān)心,其中大多數(shù)為注釋描述,關(guān)鍵的配置都與在“初級階段”時所講一致,只有少數(shù)地方需要我們略作修改。
創(chuàng)建podspec之后的項目目錄結(jié)構(gòu)是這樣的:

創(chuàng)建podspec后的項目目錄結(jié)構(gòu)

其實,你也可以把podspec文件直接放在與TFClasses平級的目錄下,但是下面幾點中所說的相對路徑就要根據(jù)實際情況做出調(diào)整了。

3. 修改庫中源碼路徑:s.source_files。

由于podspec文件是在項目根目錄的,而存放庫的類文件的目錄TFClasses與它不在同一級。因此,需要修改為下面這樣:

  # TFClasses后的通配符“**”意味包含該目錄下所有的目錄層級,“*.{h,m}”意味著包含目錄中的所有后綴為.h或.m的文件
  s.source_files  = "TestFramework/TestFramework/TFClasses/**/*.{h,m}"
  # 如果庫中的類只有一層目錄,可以不要中間那一層的通配符,改成下面的方式
  s.source_files  = "TestFramework/TestFramework/TFClasses/*.{h,m}"
  # 或者直接全部使用通配符
  s.source_files  = "TestFramework/TestFramework/TFClasses/*.*"

需要注意的是,庫中的所有類應(yīng)該與項目中的其他類是完全解耦的,即庫中的代碼不依賴項目中的其他任何代碼也能夠正常運行,否則制作出的庫在導(dǎo)入到別的項目中后肯定無法正常運行。

4. 修改庫中引用的資源路徑

s.resource、s.resources 、s.resource_bundle = { }s.resource_bundles = { }
如果庫中引用了資源文件,或者使用了Xib、Storyboard(不能直接放在TFClasses里面,即使使用了通配符"*.*",因為它們不是類文件,而是屬于資源文件),需要為這些資源文件單獨建立目錄(如果不單獨建立目錄,也是可以放在TFClasses中的,只是需要指定文件后綴名,過于麻煩且結(jié)構(gòu)混亂)。

  # 引用單個資源
  s.resource  = 'img.png'
  # 引用多個資源
  s.resources = ['TestFramework/TestFramework/TFAssets/*.*', 'TestFramework/TestFramework/TFText/*.*']

注意:上述方式是Cocoapods不建議的,但仍可以使用(這是因為有可能與導(dǎo)入該庫的項目產(chǎn)生資源文件名沖突,因為資源文件都是平鋪在mainBundle中的)。
Cocoapods推薦使用下面的方式:

  s.resource_bundle = {
    'TFAssets' => ['TestFramework/TestFramework/TFAssets.bundle/**/*.*']
  }
  # 導(dǎo)入多個資源目錄或文件
  s.resource_bundles = {
    'TFAssets' => ['TestFramework/TestFramework/TFAssets.bundle/**/*.*']
  }

這種方式Cocoapods會在導(dǎo)入該庫的項目中的mainBundle里創(chuàng)建一個bundle,名稱就是“=>”左邊的字符串。所以,如果你是直接將自己項目中的一部分代碼制作成庫的話,最好是將所有引用的資源放在一個bundle中,bundle名就是上面配置中的名稱(所以,這時需要將上面所創(chuàng)建的TFAssets修改為TFAssets.bundle)。這樣就能保證你在開發(fā)該庫的項目中和導(dǎo)入該庫的項目中調(diào)用資源文件的代碼是一致的,否則將可能會出現(xiàn)找不到文件的情況。
如果你覺得這樣比較麻煩,也可以直接使用第一種方法,但是要注意可能產(chǎn)生的命名沖突。

5. 創(chuàng)建子庫:s.subspec。

這是為了實現(xiàn)類似AFNetworking那樣導(dǎo)入后在Pods里可以分成幾個文件夾,就像下面這樣:

AFNetworking目錄結(jié)構(gòu)

如果你仔細研究過AFNetworking的話,你會發(fā)現(xiàn),它的每個文件夾(其實只有Serialization、Security、Reachability這三項)都是可以單獨導(dǎo)入項目的,你可以在podfile里這么寫:

    pod 'AFNetworking/Serialization'
    pod 'AFNetworking/Security'

為什么NSURLSession和UIKit不能單獨導(dǎo)入項目?其實并不是這樣,它們也是可以的,但是導(dǎo)入這兩個的時候,也會同時導(dǎo)入另外三項,因為它的podspec是這樣的:

s.subspec 'NSURLSession' do |ss|
    # 這里可以看出,NSURLSession是需要依賴Serialization、Reachability和Security的
    # 因此,導(dǎo)入NSURLSession時,也會同時導(dǎo)入另外三項
    ss.dependency 'AFNetworking/Serialization'
    ss.ios.dependency 'AFNetworking/Reachability'
    ss.osx.dependency 'AFNetworking/Reachability'
    ss.tvos.dependency 'AFNetworking/Reachability'
    ss.dependency 'AFNetworking/Security'

    ss.source_files = 'AFNetworking/AF{URL,HTTP}SessionManager.{h,m}', 'AFNetworking/AFCompatibilityMacros.h'
    ss.public_header_files = 'AFNetworking/AF{URL,HTTP}SessionManager.h', 'AFNetworking/AFCompatibilityMacros.h'
  end

  s.subspec 'UIKit' do |ss|
    ss.ios.deployment_target = '7.0'
    ss.tvos.deployment_target = '9.0'

    # 這里可以看出UIKit依賴NSURLSession,而NSURLSession又依賴另外三項
    ss.dependency 'AFNetworking/NSURLSession'

    ss.public_header_files = 'UIKit+AFNetworking/*.h'
    ss.source_files = 'UIKit+AFNetworking'
  end

因此,可以看出,這個所謂的子庫(s.subspec)其實是“庫中庫”。如果你希望自己的庫可以像AFNetworking那樣有多層級的文件夾,必須要清晰的規(guī)劃好自己庫中的類,使得它們之間有一部分類是完全與其他類解耦的,有一部分類可能需要依賴其他子庫中的類。但是,如果你規(guī)劃的這些文件夾之間存在相互依賴的話……我相信,你最終肯定會放棄這么做的。

但是,我可不是為了勸退大家才這么說的,只是子庫的使用確實有一定的前提,如果你的庫不滿足這樣的前提,或者你嫌麻煩不打算使用子庫的話,那么你可以直接跳過這一段了。

下面,開始正式介紹子庫的使用方式。至于怎樣解耦自己的庫和規(guī)劃文件夾,這里就不介紹了,大家各顯神通吧╮( ̄  ̄)╭。

首先,要從podspec文件的第一行代碼開始說起,為什么所有的配置項都是以“s”開頭呢?能不能以別的什么字符開頭?答案是當(dāng)然可以,它就藏在第一行代碼中:

# |s|就相當(dāng)于給這個podspec空間創(chuàng)建了一個宏,在這個“命名空間”內(nèi),“s”就代表當(dāng)前這個庫。如果你想自定義一個字符,修改這里就行了
Pod::Spec.new do |s|

然后,再來看AFNetworking里關(guān)于子庫的配置:

# 1. 子庫內(nèi)部的配置項與外面完全一致,你不需要再指定version、summary、author等固定信息,但至少需要指定source_files,其他配置項根據(jù)子庫的實際情況確定
# 2. 這里的|ss|就相當(dāng)于給Security子庫創(chuàng)建了一個宏,它的作用域僅是這個子庫。所以你會發(fā)現(xiàn),AFNetworking所有子庫的宏都是“ss”
# 3. Security是導(dǎo)入該庫的項目中顯示的子庫的名稱,它可以與你在開發(fā)該庫的項目中的文件夾名稱不同,只要你在子庫中的source_files指定正確的路徑即可。但為了避免麻煩,我想你一般都會選擇兩者一致的
s.subspec 'Security' do |ss|
    ss.source_files = 'AFNetworking/AFSecurityPolicy.{h,m}'
    ss.public_header_files = 'AFNetworking/AFSecurityPolicy.h'
    ss.frameworks = 'Security'
end

總之,子庫的關(guān)鍵在于規(guī)劃文件夾及各文件夾之間的解耦;其內(nèi)部的配置與外面是完全一致的。

到了這里,應(yīng)該能夠很輕松的制作出一個開源庫了。但是還想提醒一點,就是在開發(fā)庫的過程中,尤其是對podspec做了修改,不要忘記多使用pod lib lint命令校驗?zāi)愕呐渲檬欠裾_。

創(chuàng)建閉源庫

上面一直在說開源庫,接下來該介紹下閉源庫了。所謂閉源庫,就是指.a、.framework這樣不公開源碼的庫。
既然是要閉源,那么開發(fā)和發(fā)布肯定不能在一個地址中進行。因此,你的podspec所在的目錄應(yīng)該只包含輸出后的.framework或.a+headers,以及可能會引用的資源bundle。
至于怎么創(chuàng)建.framework或.a,網(wǎng)上都有比較多且詳細的文章,這里就不贅述了。

那么我們直接開始~

一個制作好的閉源庫,以.framework為例,應(yīng)該類似于下面這樣:


閉源庫目錄結(jié)構(gòu)

關(guān)于閉源庫的podspec,其中的配置項與上面所講幾乎完全一致,只是閉源庫沒有了s. source_files,需要修改為s. vendored_frameworks。

下面是配置代碼:

# 如果有多個.framework,可以使用英文逗號隔開
s.vendored_frameworks = 'TFFramework.framework', 'Other.framework'

# .a庫是下面這個配置,多個也可以使用英文逗號隔開
s.vendored_libraries = 'libTFLibrary.a', 'libOther.a'

上傳podspec到Cocoapods

上面提到過,如果你希望自己的庫可以這樣導(dǎo)入:

  pod 'TFFramework', '~>0.1.0'

而不是一定要指定地址:

  pod 'TFFramework' :git => 'https://github.com/RavenKite/TestFramework.git', :tag=>'0.1.0'

就必須要將該庫的podspec上傳到Cocoapods的服務(wù)器,這樣你就可以在終端通過pod search搜索到它,并且可以用上面那種簡化的方式導(dǎo)入。
但是,如果你的目的是制作私有庫的話,還是不要將podspec上傳到Cocoapods了。

1. 注冊Cocoapods

在終端輸入以下命令:

# 如果你之前注冊過Cocoapods,但是會話過期了,也需要執(zhí)行以下命令,但是可以不輸入用戶名,除非你需要修改
pod trunk register 郵箱 '用戶名'

之后你會收到一封驗證郵件,點擊郵件中的鏈接進行驗證。
驗證通過后,執(zhí)行以下命令,查看自己的信息:

pod trunk me

如果注冊成功且會話有效,則會打印出你的個人信息。

2. 校驗podspec的有效性

這個命令上面已經(jīng)提到過了,只有校驗通過(沒有ERROR)的podspec才能上傳Cocoapods。其中的警告是可以在命令后拼接--allow-warnings忽略的,但是建議還是根據(jù)提示做出修改,以達到?jīng)]有警告的程度再進行上傳。

pod lib lint TFFramework.podspec
# 忽略警告
pod lib lint TFFramework.podspec --allow-warnings
3. 上傳podspec

需要注意的是,在上傳之前,一定要在自己庫的SVN或Git倉庫創(chuàng)建tag,且tag版本與podspec中一致,否則將會上傳失敗。在GitHub上就是Draft a new release
經(jīng)過第二步的校驗通過且創(chuàng)建tag后,再執(zhí)行上傳命令:

pod trunk push TFFramework.podspec

# 如果你選擇了不處理第二步中的警告就直接上傳,需要在后面拼接'--allow-warnings'
pod trunk push TFFramework.podspec --allow-warnings

至于上傳速度方面,以我的經(jīng)驗,一般都會很慢,所以要有耐心~
上傳成功后,控制臺的結(jié)果如下:


podspec上傳成功
4. 搜索自己的庫

檢測是否上傳成功的標(biāo)識,就是能通過pod search搜索到自己的庫:

pod search TFFramework
搜索TFFramework結(jié)果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 項目組件化、平臺化是技術(shù)公司的共同目標(biāo),越來越多的技術(shù)公司推崇使用pod管理第三方庫以及私有組件,一方面使項目架構(gòu)...
    swu_luo閱讀 22,875評論 0 39
  • 一、創(chuàng)建的github倉庫原文地址 1 進入Github網(wǎng)站www.github.com登陸自己的賬號后 2 建立...
    freesan44閱讀 4,132評論 2 12
  • 孩子四月中旬春假兩周。本來約好和小伙伴一起旅行,奈何突發(fā)狀況小伙伴臨時爽約,只好帶著小男生在帝都來個本地自由行。 ...
    良仔l(wèi)z閱讀 249評論 0 0
  • 當(dāng)兩個Web組件之間為鏈接關(guān)系時,被鏈接的組件通過getParameter()方法來獲得請求參數(shù).request....
    海納百川_4d26閱讀 153評論 0 0
  • 有人說,人生在世,孤獨感是如影隨形的。這話說的多么堂而皇之,像影子一樣的孤獨。 小時候的孤獨是一個人,沒有玩具沒有...
    小懶蟲qin閱讀 672評論 0 1

友情鏈接更多精彩內(nèi)容