2018-04-04 更新:添加 pod lib create 的方式建立 framework,推薦使用該方式。
Xcode: 9.3
Swift: 4.1
CocoaPod: 1.4.0
複製代碼
本文將主要討論以下幾個問題:html
最近公司因爲和其餘公司創建了各類合做關係,「建立 SDK」工做被提上了日程,因爲以前沒有本身作過,在生成 framework 時踩了一些坑,在這裏記錄下來以作總結,若碼友能用得上則不勝開心。 工做中不免會遇到這種狀況,想把某個功能包裝起來給其餘人用,可是出於某種緣由又不想公開本身的實現方式,這時就須要靜態庫(framework 或 .a)了。(PS:.a 和 .framework 的區別能夠看下這篇文章:iOS開發-.a與.framework區別?ios
XCode -> File -> New -> Project -> Cocoa Touch Framework
複製代碼
建立 SJTutorialSDK,並新建測試類名爲 HelloWorld(Swift)、HelloOC(OC),目錄以下:git
├── SJTutorialSDK
│ ├── HelloOC.h -> 暴露的 OC 類
│ ├── HelloOC.m
│ ├── HelloWorld.swift -> 暴露的 Swift 類
│ ├── Info.plist
│ └── SJTutorialSDK.h -> 當前靜態庫頭文件
└── SJTutorialSDK.xcodeproj
├── project.pbxproj
├── project.xcworkspace
複製代碼
建立完成後的頭文件以下:github
// SJTutorialSDK.h
// SJTutorialSDK
#import <UIKit/UIKit.h>
FOUNDATION_EXPORT double SJTutorialSDKVersionNumber;
FOUNDATION_EXPORT const unsigned char SJTutorialSDKVersionString[];
// 第三方庫的頭文件
#import <SJDemoSDK/Animals.h>
// 對外暴露的頭文件(僅限於 OC,Swift 添加 public 後會自動導入)
#import <SJTutorialSDK/HelloOC.h>
複製代碼
Build Phases 配置以下: swift
Build Settings 中 Mach-o Type 設置爲 Static Library。 至此,靜態庫.framework 建立完畢。xcode
咱們開發過程當中常常提到的 arm64,x86_64 具體是什麼東西呢?這裏能夠參考這篇文章: iOS 中的 armv7,armv7s,arm64,i386,x86_64 都是什麼。 簡單點來講:模擬器 32/64 位處理器分別須要 i386/x86_64 架構,真機 32/64 位處理器分別須要 armv7(armv7s)/arm64 架構。 因此,若是你構建的靜態庫只須要支持 iPhone 5S 及以上(或 iPad mini2 及以上)的真機和模擬器,那麼你的靜態庫將只需支持 arm64 和 x86_64 架構。bash
1.調整到 Release(發佈)模式:Edit Scheme -> Run -> Info -> Build Configuration -> Release; 架構
2.分別使用真機、模擬器編譯,生成對應的 SJTutorialSDK.framework;app
3.合併 frameworks框架
# 查看靜態庫支持的架構
lipo -info xxx
# 合併 xxx1 和 xxx2,最後的文件支持二者支持的全部架構
lipo -create xxx1 xxx2 -output xxx1
eg:
lipo -creat Release-iphoneos/SJTutorialSDK.framework/SJTutorialSDK Release-iphonesimulator/SJTutorialSDK.framework/SJTutorialSDK -output Release-iphoneos/SJTutorialSDK.framework/SJTutorialSDK
複製代碼
生成 framework 時踩過的坑:-output 的文件是 xxx,而不是 xxx.framework
Architectures in the fat file: /Users/shijian/Desktop/SJTutorialSDK.framework/SJTutorialSDK are: x86_64 arm64
# congratulations
複製代碼
在 iOS 中,能夠經過 Bundle 文件管理資源文件(圖片,語音,視頻,plist,xib,storyboard等),Bundle 文件實際上就是個普通的文件夾,只是在名字中添加了 .Bundle 的後綴而已。 對圖片的命名最好添加上 @3x/@2x,這樣系統會自動放在對應的位置,不須要咱們額外的操做。
.
└── SJTutorialRes.Bundle
├── alipay@3x.png
└── wechat@3x.png
複製代碼
.
├── Base.lproj
│ ├── LaunchScreen.nib
│ └── Main.storyboardc
│ ├── Info.plist
│ ├── UIViewController-vXZ-lx-hvc.nib
│ └── vXZ-lx-hvc-view-kh9-bI-dsS.nib
├── Frameworks
│ ├── HHMedicSDK.framework
│ │ ├── ControlView.nib
│ │ ├── HHMedicSDK
│ │ ├── HHPB.bundle
│ │ │ ├── HHCameraCancleCap@2x.png
│ │ │ ├── camear_del_btn_normal@2x.png
│ │ ├── HMSDK.bundle
│ │ │ ├── angle-mask.png
│ │ │ └── success@3x.png
│ │ ├── Info.plist
│ └── libswiftsimd.dylib
├── HHMedicSDK_Example
├── Info.plist
├── PkgInfo
├── _CodeSignature
│ └── CodeResources
└── libswiftRemoteMirror.dylib
複製代碼
注:本文中的 bundle 指自建的資源文件, Bundle 指代 swift 的類(對應 OC 中的 NSBundle)。 咱們能夠知道 Bundle.main 指代當前根目錄,那麼如何獲取 camear_del_btn_normal@2x.png 這張圖片呢?經過路徑咱們能夠知道其路徑爲 ./Frameworks/HHMedicSDK.framework/HHPB.bundle/camear_del_btn_normal@2x.png。那麼一種簡單的獲取方式便明瞭了:Bundle.main.url(forResource: "Frameworks/HHMedicSDK.framework/HHPB.bundle/camear_del_btn_normal@2x", withExtension: "png"),固然也能夠經過層層的 urlForResoure 來讀取。
轉過來再想一想,獲取 bundle 的方式就簡單了。咱們能夠事先不用苦苦思考資源 bundle 的具體生成位置,先 debug 獲取 app,而後再打開 app 查看各個 Bundle 對應位置,最後就能夠再回過來重寫鍵入獲取方式,大功告成。
class HHResManager {
static func getImg(_ name: String) -> UIImage? {
let url = Bundle.main.url(forResource: "Frameworks/HHMedicSDK", withExtension: "framework") ?? URL(fileURLWithPath: "")
let bundle = Bundle(url: url)?.url(forResource: "HMSDK", withExtension: "bundle") ?? URL(fileURLWithPath: "")
return UIImage(named: name, in: Bundle(url: bundle), compatibleWith: nil)
}
static func xibBundle() -> Bundle {
let apath = Bundle.main.path(forResource: "Frameworks/HHMedicSDK", ofType: "framework")!
return Bundle(path: apath)!
}
static func getAudio(_ name: String) -> URL? {
let url = Bundle.main.url(forResource: "Frameworks/HHMedicSDK", withExtension: "framework") ?? URL(fileURLWithPath: "")
guard let bundle = Bundle(url: url)?.url(forResource: "HMSDK", withExtension: "bundle") else { return nil }
guard let path = Bundle(url: bundle)?.path(forResource: name, ofType: "mp3") else { return nil }
return URL(string: path)
}
}
複製代碼
至此,當前建立的靜態庫 SJTutorialSDK 有以下特性:支持 OC 和 Swift 混編,引用了其餘第三方庫,支持資源文件讀取,支持多種架構。
若是本身的靜態庫是私有的,能夠跳過 trunk 環節,直接在本身的代碼倉庫中建立好倉庫,而後配置相應的 podspec 文件,pod 引用時指定對應的路徑便可。但大多數狀況下仍是要給其餘人使用的,咱們固然也能夠不經過 trunk,直接在引用時指定地址來使用當前倉庫,可是這樣用起來老是不那麼直觀。
# 經過名稱和指定地址使用
pod 'SJTutorialSDK',:git => "git@code.XXX/SJTutorialSDK.git"
# 經過名稱
pod 'SJTutorialSDK'
複製代碼
二者對比起來,高下立判。因此建議仍是經過 trunk 來 push 本身的代碼。
如何在 github 上新建倉庫,網上已經有不少的教程,這裏再也不贅述,這裏主要談談配置 podspec 文件。
podSpec 官方解釋: A Podspec, or Spec, describes a version of a Pod library. ,其實就是一個描述 pod 庫的信息(版本,依賴,做者,描述,系統庫等)的文件。
Pod::Spec.new do |s|
s.name = "podSDK"
s.version = "0.0.1"
s.summary = "當前庫的總結。"
s.description = <<-DESC
描述文件
DESC
s.homepage = "http://EXAMPLE/podSDK"
s.license = "MIT"
s.author = { "shmily" => "shmilyshijian@foxmail.com" }
s.platform = :ios, "9.0"
s.source = { :git => "https://github.com/515783034/podSDK.git", :tag => "#{s.version}" }
s.resources = "podSDK/Resources/*.*"
# 本庫提供的framework靜態庫
s.vendored_frameworks = 'podSDK/Sources/*.framework'
#################
# 依賴的系統動態庫
# s.frameworks = "SomeFramework", "AnotherFramework"
# 依賴的系統靜態庫
# s.libraries = "iconv", "z"
# 本庫提供的 .a 靜態庫
#s.vendored_libraries = 'podSDK/Sources/*.a'
# 本庫添加的第三方依賴庫
#s.dependency "SJLineRefresh", "~> 1.4"
end
複製代碼
關於 podspec 的內容,能夠查看我以前寫過的文章:建立公共/私有pod --podspec,這裏也再也不過多贅述。 因爲當前倉庫只是引用了幾個靜態庫,因此 sources 能夠不配置,只須要配置 vendored_frameworks 便可。
pod lib lint
# 若是驗證中有警告,能夠添加參數 --allow-warnings 能夠忽略
複製代碼
pod trunk push SJTutorialSDK.podspec
# 忽略警告的方式同上
複製代碼
git tag -m "desc" 1.0.0
git push --tag
# podspec 中修改對應的 s.version
複製代碼
上傳成功,enjoy
🎉 Congrats
🚀 podSDK (0.0.1) successfully published
📅 March 19th, 05:04
🌎 https://cocoapods.org/pods/podSDK
👍 Tell your friends!
複製代碼
上面講到咱們經過 Cocoa Touch Framework 建立 framework,而後再上傳到 CocoaPod 供別人使用,可是在使用這種方式的過程當中遇到了幾個問題。 使用 Xcode 建立 framework,爲了測試咱們固然能夠經過在當前工程的基礎上再 File -> New -> Target -> Single View App 對 framework 進行測試,可是在實際使用中遇到了幾個問題,經過拖拽 framework 的方式與 CocoaPod 管理的方式有些出入,會致使部分功能有差別,好比上面提到的 Bundle 問題,在兩者表現上徹底不同,再好比在某些 Build Settings 的設置上也會有一些差異,總之這不是個友好的方式。 既然使用了 CocoaPod 管理 framework,那麼索性也用 CocoaPod 來"生成" Framework 吧,都是同一套東西,用起來也不會有各類各樣匪夷所思的問題。
官方 pod lib create 命令使用詳解:Using Pod Lib Create 使用起來很是簡單,只須要 經過 pod lib create xxxx
而後選擇相關配置便可。
What platform do you want to use?? [ iOS / macOS ] (選擇平臺)
>
ios
What language do you want to use?? [ Swift / ObjC ] (選擇語言)
>
swift
Would you like to include a demo application with your library? [ Yes / No ] (是否包含 demo)
>
yes
Which testing frameworks will you use? [ Quick / None ] (測試框架)
> None
Would you like to do view based testing? [ Yes / No ]
> No
Running pod install on your new library.
複製代碼
目錄大體以下(其中篇幅緣由部分不相關目錄已移除):
.
├── Example
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ │ ├── Headers
│ │ ├── Local Podspecs
│ │ ├── Manifest.lock
│ │ ├── Pods.xcodeproj
│ │ │ ├── project.pbxproj
│ │ └── Target Support Files
│ ├── SJPlcSDK
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.xib
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── ViewController.swift
│ ├── SJPlcSDK.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ ├── SJPlcSDK.xcworkspace
├── LICENSE
├── README.md
├── SJPlcSDK (源代碼和資源等)
│ ├── Assets
│ └── Classes
│ └── ReplaceMe.swift
├── SJPlcSDK.podspec (倉庫配置文件)
└── _Pods.xcodeproj
複製代碼
添加代碼後,配置 podspec 文件(上文已給出,不贅述),可打開 ./Example/SJPlcSDK.xcworkspace 進行測試。
可在 ./Pods -> targets -> SJPlcSDK -> Build Settings 下進行 framework 的相關配置。
lipo -create -output
命令合併便可(詳細操做請看上文)。
有了 framework 和相關資源文件後,發佈和上面相同。提交代碼,打 tag,pod trunk push。
靜態庫的建立並無太複雜的操做,主要步驟以下: