修改於:2017.1.24
1.什么是庫?
庫是程序代碼的集合,是共享程序代碼的一種方式
2.根據源代碼的公開情況,庫可以分為2種類型
a.開源庫
公開源代碼,能看到具體實現 ,比如SDWebImage、AFNetworking
b.閉源庫
不公開源代碼,是經過編譯后的二進制文件,看不到具體實現。主要分為:靜態庫、動態庫
3.靜態庫和動態庫的存在形式
靜態庫:以.a 和 .framework為文件后綴名。
動態庫:以.tbd(之前叫.dylib) 和 .framework 為文件后綴名。
.a是純二進制文件,.a文件不能單獨使用,至少要有.h文件配合,而.framework除了二進制文件外,還包含一些資源文件(頭文件,plist等),由於自身包含了頭文件,所以.framework可以單獨使用。
.a和.framework兩種靜態庫,通常都是把需要用的到圖片或者xib文件存放在一個bundle文件中,而該bundle文件的名字和.a或.framework的名字相同。
ios 8 以后蘋果放開了動態庫。
4.靜態庫和動態庫在使用上的區別
靜態庫:鏈接時會被完整的復制到可執行文件中,被多次使用就有多份拷貝。
動態庫:鏈接時不復制,程序運行時由系統動態加載到內存,系統只加載一次,多個程序共用(如系統的UIKit.framework等),節省內存。
5.創建靜態庫可能出於以下幾個理由:
1.你想將工具類代碼或者第三方插件快捷的分享給其他人而無需拷貝大量文件。
2.你想讓一些通用代碼處於自己的掌控之下,以便於修復和升級。
3.你想將庫共享給其他人,但不想讓他們看到你的源代碼。
4.比如很老的框架使用的是MRC環境下面的框架,那么我們只需要將其打包成靜態庫就可以了,這樣就不用擔心是不是ARC環境了。
6、iOS設備架構
模擬器:
iPhone4s-iPnone5:i386
iPhone5s-iPhone7 Plus:x86_64
真機:
iPhone3gs-iPhone4s: armv7
iPhone5-iPhone5c: armv7s
iPhone5s-iPhone7 Plus: arm64
支持armv7的靜態庫可以在armv7s上正常運行。
Xcode創建靜態庫詳解(Cocoa Touch Static Library)(本人使用的是Version 6.3)
一、創建靜態庫文件(.a 文件)
打開Xcode, 選擇File ----> New ---> Project。
選擇iOS ----> Framework & Library ---> Cocoa Touch Static Library。
點擊Next。按照流程一步一步的創建工程。



注意靜態庫文件的版本(4種)
1.真機-Debug版本
2.真機-Release版本
3.模擬器-Debug版本
4.模擬器-Release版本
就是在build configuration 里面進行調整一下debug和release,然后分別在真機和模擬器上面進行編譯。

結果如下圖:

調試版本(Debug版本) VS 發布版本(Release版本)
--------------------------------------------------------------------------------
- 調試版本會包含完整的符號信息,以方便調試
- 調試版本不會對代碼進行優化
- 發布版本不會包含完整的符號信息
- 發布版本的執行代碼是進行過優化的
- 發布版本的大小會比調試版本的略小
- 在執行速度方面,調試版本會更快些,但不意味着會有顯著的提升
所以我們建議在產品即將上線的時候要進行如下圖的調整:


二、應用靜態庫文件(.a 文件)
1.想讓靜態庫文件給別人使用,需要將頭文件暴露給別人。按着下面的步驟將頭文件添加進來。


再點擊libstatic.a右擊show In Finder 就可以查看如下圖:

2.然后將靜態庫拖進項目中。就能利用頭文件了

3.因為無論是模擬器還是真機都有不同的架構。所以經常會出現如下找不到某個架構的錯誤

為了解決各個機型的模擬器都能用可以有2種方法:
1.合並各個靜態庫(在終端中執行如下操作)

$ cd /Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products/Release-iphonesimulator5s
$ lipo -info libstatic.a
Architectures in the fat file: libstatic.a are: i386 x86_64
$ cd /Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products/Release-iphonesimulator6
$ lipo -info libstatic.a
Architectures in the fat file: libstatic.a are: i386 x86_64
1》.我們可以cd到.a文件所在文件夾的當前目錄
2》.再執行 lipo -info 靜態庫.a文件
這樣就可以查詢該靜態庫支持的架構是什么。
回到.a文件所在文件夾所在的文件夾目錄:cd ..
$ cd ..
$ pwd
/Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products

合並2個靜態庫
$ cd ..
$ pwd
/Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products
$ lipo -create Release-iphonesimulator5s/libstatic.a Release-iphonesimulator6/libstatic.a -output lib.a
fatal error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: Release-iphonesimulator5s/libstatic.a and Release-iphonesimulator6/libstatic.a have the same architectures (i386) and can't be in the same fat output file
由於2個靜態庫都含有相同的架構,所以出現錯誤,因為我們合並的是Release-iphonesimulator6和Release-iphonesimulator5s之間的版本,都是模擬器的。其實我們在制作靜態庫的時候,無論是在模擬器還是真機的時候,設置Build Active Architecture Only為no的話,一次打包,如果是模擬器就會適用所有機型的模擬器,如果是真機就會適用所有機型的真機。實際上我們通常合並的是模擬器和真機的靜態庫。模擬器和真機版本合並完后,我們姑且稱之為“合並版本”吧,那么分別會有debug合並版本和release合並版本,通常我們會在上線前將debug合並版本換成relaese合並版本(當然release合並版本會支持模擬器和真機)。目前還沒找到release合並版本和debug合並版本合並的方法(估計有,但是不會是這么簡單的合並)。
合並模擬器和真機的靜態庫,步驟如下:
合並前:

$ cd /Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products/Debug-iphonesimulator
$ lipo -info libstatic.a
input file libstatic.a is not a fat file
Non-fat file: libstatic.a is architecture: x86_64
$ cd /Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products/Debug-iphoneos
$ lipo -info libstatic.a
Architectures in the fat file: libstatic.a are: armv7 arm64
$ cd ..
$ pwd
/Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products
$ lipo -create Debug-iphonesimulator/libstatic.a Debug-iphoneos/libstatic.a -output lib.a
合並后:

合並后的lib.a同時支持:armv7 x86_64 arm64 架構。(可以看出在制作模擬器的靜態庫的時候並沒有設置Build Active Architecture Only為no)
$ cd /Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products
$ pwd
/Users/XQ/Library/Developer/Xcode/DerivedData/static-bdsgvzfqigaspiaeivnhmmygrhgj/Build/Products
$ lipo -info lib.a
Architectures in the fat file: lib.a are: armv7 x86_64 arm64
2.可以通過配置

小結一下:
1.編譯靜態庫:項目->Build Phases->Copy File->添加頭文件
2.模擬器編譯時,挑選高版本(向下兼容,低版本不能在高版本運行)
3.靜態庫分真機版本和模擬器版本(必須在對應的版本運行)
4.合並真機版本和模擬器版本(常用)
lipo -create 真機.a 模擬器.a -output 結果.a
合並版本更大,開始時使用方便(所以可以開發時使用合並版本,發布時,使用真機版本)
5.release版本和debug版本:
debug版本:調試版本,沒有任何優化,也就是說各種錯誤信息,都將拋出和檢測,相對來說性能低一點,但是方便調試
release版本:發布版本,進行了優化,執行效率更高
提醒:實際開發當中,項目完成后,在debug版本上沒有問題了,一定要去release版本上調試一下,否則也許可能發生一些bug。
三、調試靜態庫文件(.a 文件)
因為靜態庫也是需要不斷的開發,調試,最終才能完美,所以我們應該是不斷的開發,不斷的調試,那么像上面的那種方式直接建立一個靜態庫項目就很麻煩,所以我們應該是在某一個項目中添加一個靜態庫文件,那么就可以做到不斷開發,不斷調試。
1.添加靜態庫target:項目->General->左下角+->添加靜態庫(StaticLib)
2.在StaticLib文件夾內就可以就行開發靜態庫
3.項目引入靜態庫:項目->General->Linked Frameworks and Libraries->添加靜態庫
4.導出靜態庫:選擇左上角房子->同之前導出方式(也就是分別在模擬器和真機上面進行編譯)。



(注意:如果是動態庫的話,上面的Embedded Binaries 也要導入相應的庫 )
總體思路是我們在項目中新添加靜態庫target,然后在項目中導入靜態庫文件,這個時候scheme選中原本項目(注意是項目),進行編譯看是否通過。所以我們可以通過這樣的方法進行調試靜態庫,如果真的調試到沒有錯誤的時候,可以將scheme處選擇成小房子(靜態庫),然后編譯就可以生成相應的靜態庫。這樣我們就可以邊開發邊經行靜態庫的調試。
2017.1.24增加腳本打包方法如下:
建立一個Aggregate target (使用Xcode Version 8.2.1 (8C1002))
從上面可以看出來,因為.a庫是會生成模擬器和真機兩個不同的包的,下面嘗試利用腳本來合成。
1、我們先按照上面的步驟流程建立一個.a靜態庫(單獨的靜態庫)yooweiSDKTest
2、建立一個Aggregate target,在Aggregate里面執行腳本。

3、新建一個運行腳本

4、接下來我們就可以在Run Script里寫我們的腳本了

把下面的腳本復制到Run Script里面:
if [ "${ACTION}" = "build" ] then #要build的target名 target_Name=${PROJECT_NAME} echo "target_Name=${target_Name}" #build之后的文件夾路徑 build_DIR=${SRCROOT}/build echo "build_DIR=${build_DIR}" #真機build生成的頭文件的文件夾路徑 DEVICE_DIR_INCLUDE=${build_DIR}/Release-iphoneos/include/${PROJECT_NAME} echo "DEVICE_DIR_INCLUDE=${DEVICE_DIR_INCLUDE}" #真機build生成的.a文件路徑 DEVICE_DIR_A=${build_DIR}/Release-iphoneos/lib${PROJECT_NAME}.a echo "DEVICE_DIR_A=${DEVICE_DIR_A}" #模擬器build生成的.a文件路徑 SIMULATOR_DIR_A=${build_DIR}/Release-iphonesimulator/lib${PROJECT_NAME}.a echo "SIMULATOR_DIR_A=${SIMULATOR_DIR_A}" #目標文件夾路徑 INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME} echo "INSTALL_DIR=${INSTALL_DIR}" #目標頭文件文件夾路徑 INSTALL_DIR_Headers=${SRCROOT}/Products/${PROJECT_NAME}/Headers echo "INSTALL_DIR_Headers=${INSTALL_DIR_Headers}" #目標.a路徑 INSTALL_DIR_A=${SRCROOT}/Products/${PROJECT_NAME}/lib${PROJECT_NAME}.a echo "INSTALL_DIR_A=${INSTALL_DIR_A}" #判斷build文件夾是否存在,存在則刪除 if [ -d "${build_DIR}" ] then rm -rf "${build_DIR}" fi #判斷目標文件夾是否存在,存在則刪除該文件夾 if [ -d "${INSTALL_DIR}" ] then rm -rf "${INSTALL_DIR}" fi #創建目標文件夾 mkdir -p "${INSTALL_DIR}" #build之前clean一下 xcodebuild -target ${target_Name} clean #模擬器build xcodebuild -target ${target_Name} -configuration Release -sdk iphonesimulator #真機build xcodebuild -target ${target_Name} -configuration Release -sdk iphoneos #復制頭文件到目標文件夾 cp -R "${DEVICE_DIR_INCLUDE}" "${INSTALL_DIR_Headers}" #合成模擬器和真機.a包 lipo -create "${DEVICE_DIR_A}" "${SIMULATOR_DIR_A}" -output "${INSTALL_DIR_A}" #打開目標文件夾 open "${INSTALL_DIR}" fi
5、運行程序
注意scheme選擇yooweiShell,之后正常的run就行了。編譯成功以后,如下:

我們這個腳本,將合成后的.a庫和頭文件都按照我們想要的方式放好了。其他工程要用這個SDK的話,既可以直接拉MySDK文件夾過去就可以了。如果你想要其他的放法,比如不要Headers文件夾,那就,改一下腳本就可以了。
6、驗證
在終端使用命令 “lipo -info .a文件路徑”查看
$ lipo -info /Users/galahad/Downloads/yooweiSDKTest/Products/yooweiSDKTest/libyooweiSDKTest.a
Architectures in the fat file: /Users/galahad/Downloads/yooweiSDKTest/Products/yooweiSDKTest/libyooweiSDKTest.a are: armv7 i386 x86_64 arm64
說明成功了
7、腳本解釋
本代碼中用到的核心命令:xcodebuild ,主要用來編譯Xcode的工程。可以在終端中輸入xcodebuild -h來查看命令的詳情
介紹一下本腳本中用到的幾個參數:
clean
clean一下工程-configuration Release
使用Release方式編譯,還可以使用Debug-sdk iphoneos
真機編譯,還可以使用-sdk iphonesimulator模擬器編譯
cp "源文件路徑" "目標文件路徑"
復制"源文件路徑"的文件到 "目標文件路徑"lipo -create "模擬器.a文件路徑" "真機.a文件路徑" -output "目標.a文件路徑"
將模擬器和真機的.a包合成。
用到的一些shell腳本基礎命令:
(1)echo "想要查看的日志或重要東西" ,echo相當於OC中的NSLog。運行腳本后,可以在這里找到log

(2)變量名=變量值
賦值命令。比如將"CrazyStone"賦值給MyName變量
MyName=CrazyStone
(3)${變量名}
取出變量名的內容。比如:取出變量MyName中的內容
${MyName}
(4)判斷語句
if [ 條件語句 ]then ... fi
條件語句為真就執行then后面的語句,不成立就結束判斷語句
本腳本中用到的判斷語句:[ -d "文件夾路徑" ] :判斷是否為文件夾
腳本結構解釋
看完上面,我想你再看一下代碼應該就能理解腳本,然后可以做一些簡單的改動了。下面再介紹一下腳本的結構。
if [ "${ACTION}" = "build" ] then #我們的大部分腳本代碼 fi
執行腳本的時候做個判斷,在Xcode里面build這個工程的時候就執行then后面的腳本
#要build的target名 target_Name=${PROJECT_NAME} echo "target_Name=${target_Name}"
變量target_Name是我們要編譯的target的名字,在這里指的是工程的名字${PROJECT_NAME},也就是yooweiSDKTest。
順便說一下,ACTION和PROJECT_NAME都是Xcode里面定義的,這是在Xcode里面寫腳本的一個好處。
#build之后的文件夾路徑 build_DIR=${SRCROOT}/build echo "build_DIR=${build_DIR}" #真機build生成的頭文件的文件夾路徑 DEVICE_DIR_INCLUDE=${build_DIR}/Release-iphoneos/include/${PROJECT_NAME} echo "DEVICE_DIR_INCLUDE=${DEVICE_DIR_INCLUDE}" #真機build生成的.a文件路徑 DEVICE_DIR_A=${build_DIR}/Release-iphoneos/lib${PROJECT_NAME}.a echo "DEVICE_DIR_A=${DEVICE_DIR_A}" #模擬器build生成的.a文件路徑 SIMULATOR_DIR_A=${build_DIR}/Release-iphonesimulator/lib${PROJECT_NAME}.a echo "SIMULATOR_DIR_A=${SIMULATOR_DIR_A}"
這里是定義的build之后各個文件的路徑。我們執行了xcodebuild命令之后,會在工程目錄生成一個build文件夾,里面有build之后生成的文件。打開Finder看看就知道各個文件的路徑了。
#目標文件夾路徑 INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME} echo "INSTALL_DIR=${INSTALL_DIR}" #目標頭文件文件夾路徑 INSTALL_DIR_Headers=${SRCROOT}/Products/${PROJECT_NAME}/Headers echo "INSTALL_DIR_Headers=${INSTALL_DIR_Headers}" #目標.a路徑 INSTALL_DIR_A=${SRCROOT}/Products/${PROJECT_NAME}/lib${PROJECT_NAME}.a echo "INSTALL_DIR_A=${INSTALL_DIR_A}"
這里就是定義目標變量的路徑了。你想把文件放在哪里?在這里定義咯。${SRCROOT}表示工程的根目錄。用了這么久的Xcode,這個有用過吧(全局頭文件配置過吧?)?
#判斷build文件夾是否存在,存在則刪除 if [ -d "${build_DIR}" ] then rm -rf "${build_DIR}" fi #判斷目標文件夾是否存在,存在則刪除該文件夾 if [ -d "${INSTALL_DIR}" ] then rm -rf "${INSTALL_DIR}" fi #創建目標文件夾 mkdir -p "${INSTALL_DIR}"
這里就是文件的操作了。如果有這兩個文件夾,就刪除掉。為什么?為了保證我們工程的純凈啊。
#build之前clean一下 xcodebuild -target ${target_Name} clean #模擬器build xcodebuild -target ${target_Name} -configuration Release -sdk iphonesimulator #真機build xcodebuild -target ${target_Name} -configuration Release -sdk iphoneos
這里就跟平常操作一樣了。先clean一下工程,然后模擬器編譯一次,真機編譯一次。
#復制頭文件到目標文件夾 cp -R "${DEVICE_DIR_INCLUDE}" "${INSTALL_DIR_Headers}" #合成模擬器和真機.a包 lipo -create "${DEVICE_DIR_A}" "${SIMULATOR_DIR_A}" -output "${INSTALL_DIR_A}"
關鍵代碼。拷貝頭文件到我們的目標位置去。合成.a包。大功告成。
#打開目標文件夾 open "${INSTALL_DIR}"
最后,打開文件夾。檢查一下文件是否真正生成了。
