一直未間斷SDK的工作,總是在做到現(xiàn)在從未總結(jié),現(xiàn)在總結(jié)一下,備錄一下,供大家參考和借鑒。
一、什么是庫?
共享代碼便是庫,實現(xiàn)代碼的復用,一般分為靜態(tài)庫和動態(tài)庫。
二、靜態(tài)庫和動態(tài)庫的區(qū)別?
靜態(tài)庫:鏈接時完整的拷貝到可執(zhí)行文件,多次使用多次拷貝,造成冗余,使包變的更大。
動態(tài)庫:鏈接時不復制,程序運行時由系統(tǒng)加在到內(nèi)存中,供系統(tǒng)調(diào)用,系統(tǒng)加在一次,多次使用,共用節(jié)省內(nèi)存。
三、iOS的靜態(tài)庫?
.a和.framework 樣式
四、iOS的動態(tài)庫?
.dylib和.framework
五、為什么framework既是靜態(tài)又是動態(tài)?
系統(tǒng)的framework是動態(tài)的,我們自己創(chuàng)建的是靜態(tài)的。
六、.a 和 .framework 的區(qū)別是什么?
.a 是單純的二進制文件,.framework是二進制問價+資源文件。
其中.a 不能直接使用,需要 .h文件配合,而.framework則可以直接使用。
.framework = .a + .h + sorrceFile(資源文件)
七、為什么使用靜態(tài)庫?
共享代碼,方便使用。
實現(xiàn)代碼的模塊化,固定的業(yè)務模塊話,減少開發(fā)的重復勞動。
和別人分享代碼,但又不想讓別人知道代碼的具體實現(xiàn)。
八、實現(xiàn)靜態(tài)庫的注意事項:
1 、注意理解:無論是.a靜態(tài)庫還.framework靜態(tài)庫,我們需要的都是二進制文件+.h+資源文件,不同的是,.a本身只是二進制文件,需要配上.h和資源文件才能使用,而.framework本身已經(jīng)包含了二進制文件、.h和資源文件,可以直接使用。
2 、圖片資源的處理:兩種靜態(tài)庫,一般都是把圖片文件單獨的放在一個.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。新建一個文件夾,把它改名為.bundle,右鍵->顯示包內(nèi)容,之后就可以向其中添加資源文件。
3 、把category打成靜態(tài)庫,但是在使用靜態(tài)庫的工程中,調(diào)用category中的方法時會有找不到該方法的運行時錯誤(selector not recognized),解決辦法是:在使用靜態(tài)庫的工程中配置other linker flags的值為-ObjC。
4 如果一個靜態(tài)庫很復雜,需要暴露的.h比較多的話,就可以在靜態(tài)庫的內(nèi)部創(chuàng)建一個.h文件(一般這個.h文件的名字和靜態(tài)庫的名字相同),然后把所有需要暴露出來的.h文件都集中放在這個.h文件中,而那些原本需要暴露的.h都不需要再暴露了,只需要把.h暴露出來就可以了。
封裝framework
1、打開xcode,新建工程

2、創(chuàng)建功能類

3、實現(xiàn)功能類
.h文件

.m文件

4、Xcode項目配置
將framework設置成靜態(tài)庫

5、設置header,將需要暴露的頭文件放在public下面,隱藏在project或者private下面無法被引用。

然后需要在mySDk.h(必須是公開的,否則無法引用)中將你所有要公開的.h引入。

修改下面:如果是YES,說明當前活躍的版本是8,如果只編譯此機型,就設置成YES,適配所有的架構(gòu)Architecture 設置為No。

打包
手動打包
1、選中模擬器,command+B
2、選中真機,command+B
3、在finder中找到framework文件

會發(fā)現(xiàn),真機和模擬器的包

4、通過終端命令將兩個framework合為一個模擬器和真機都可使用的framework。
打開終端,輸入lipo -create命令,將
Debug-iphoneos下mySdk.framework目錄下的mySDk.framework文件

拖拽到終端中,會自動有空格。然后將Debug-iphonesimulator下mySDk.framework目錄下的mySDk.framework文件

拖拽進來,也會自動有空格,然后輸入 -output,敲空格,在引入一個新的路徑。最后敲回車,這樣就合并了。

將生成的文件拖回上面的一個mySDk.framework的文件夾中,就生成我們最終的framework。
5、使用
將framework拖入新的項目

將framework添加到

假如還是不可以,設置framework和.h的搜索路徑



第二種方法
1、創(chuàng)建.a 靜態(tài)庫

2、.h文件

.m文件

3.添加腳本生成
set -e
if [ ${DEPLOYMENT_LOCATION} == "YES" ]; then
echo "Deploying, exit"
exit 0
fi
export FRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"
rm -rf ${FRAMEWORK_LOCN}
# Create the path to the real Headers die
mkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers"
# Create the required symlinks
/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"
/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \
"${FRAMEWORK_LOCN}/${PRODUCT_NAME}"
echo "taget built dir=${TARGET_BUILD_DIR},public headers path=${PUBLIC_HEADERS_FOLDER_PATH}"
# Copy the public headers into the framework
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \
"${FRAMEWORK_LOCN}/Versions/A/Headers"
echo "Framework built successfully"
4、xcode 配置

1、選中TARGETS下的工程,點擊上方的Editor,選擇Add Target創(chuàng)建一個Aggregate.

2、嵌入腳本。選中剛剛創(chuàng)建的Aggregate,然后選中右側(cè)的Build Phases,點擊左下方加號,選擇New Run Script Phase

3、腳本是:
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
#RW_BUNDLE_NAME="VKSdkResources"
function build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
-target "${TARGET_NAME}" \
-configuration "${CONFIGURATION}" \
-sdk "${1}" \
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR="${BUILD_DIR}" \
OBJROOT="${OBJROOT}" \
BUILD_ROOT="${BUILD_ROOT}" \
SYMROOT="${SYMROOT}" $ACTION
}
function make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
#xcrun
lipo -create "${1}" "${2}" -output "${3}"
}
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo "Could not find other platform build directory."
exit 1
fi
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${SRCROOT}/${RW_FRAMEWORK_NAME}.framework"
open ${SRCROOT}
4、運行

將會生成framework在本文件中,并會自動打開文件夾
5、測試

運行之前需要查看framework是否已經(jīng)拖入

總結(jié):
1、.h文件的一定要包含自己想要暴露的。
2、開始打包的時候,一定要在選中模擬器和選中真機上邊分別編譯一次,選用的我第二的方法,可以直接運行framework。
3、調(diào)用的時候分清楚是類方法還是實例方法。
4、在制作framework或者lib的時候,如果使用了category,則使用改FMWK的程序運行時會crash,此時需要在該工程中 other linker flags添加兩個參數(shù) -ObjC -all_load。(這點沒有親測)
5、帶有資源文件的需要把圖片打包成Bundle文件,和framework一起拷貝到相應的項目中。
6、公開的類中如果引用的private的類,打包以后對外會報錯,找不到那個private的類,可以把那個private的.h放到(也沒親測)
7、namespace 沖突。靜態(tài)庫用了某第三方庫,項目也用了同樣的第三方庫,在編譯的時候就會有 duplicate symbol 錯誤,因為有兩份同樣的第三方庫。解決辦法就是把用到的第三方庫加上自定義前綴,包括類名、delegate 協(xié)議、常量名,尤其需要注意 Category 的方法名要修改。
8、封裝靜態(tài)庫的時候應盡量避免引入重量級第三方庫。
9、一個靜態(tài)庫要有自己獨有的前綴,所有類名、常量等都要加同樣的前綴。
10、真機+模擬器支持。Xcode 默認只會用當前環(huán)境(真機或模擬器)生成靜態(tài)庫,這樣的 SDK 不方便其他項目開發(fā)時調(diào)試。解決辦法就是通過腳本生成一份通用庫,build_universal_library.sh,via SO.
11、圖片等資源文件用 bundle 方式打包。一個簡單制作 bundle 的方法:新建文件夾,重命名為 YourSDK.bundle,然后 Show Package Contents 打開,加入圖片。使用圖片的時候需要指明 bundle: [UIImage imageNamed:@"YourSDK.bundle/img.png"]。也可以用 Target 方式制作 bundle,比如 iOS Library With Resourceshttp://www.galloway.me.uk/tutorials/ios-library-with-resources/.
12、如果 SDK 有用到 Category**,注意項目設置 Other Linker Flags 添加 -ObjC。(后邊介紹了-ObjC的作用)
注意:
編譯過程:
從C代碼到可執(zhí)行文件經(jīng)歷的步驟是:源代碼 > 預處理器 > 編譯器 > 匯編器 > 機器碼 > 鏈接器 > 可執(zhí)行文件
在最后一步需要把.o文件和C語言運行庫鏈接起來,這時候需要用到ld命令。源文件經(jīng)過一系列處理以后,會生成對應的.obj文件,然后一個項目必然會有許多.obj文件,并且這些文件之間會有各種各樣的聯(lián)系,例如函數(shù)調(diào)用。鏈接器做的事就是把這些目標文件和所用的一些庫鏈接在一起形成一個完整的可執(zhí)行文件。Other linker flags設置的值實際上就是ld命令執(zhí)行時后面所加的參數(shù)
下面逐個介紹3個常用參數(shù):
-ObjC:加了這個參數(shù)后,鏈接器就會把靜態(tài)庫中所有的Objective-C類和分類都加載到最后的可執(zhí)行文件中
-all_load:會讓鏈接器把所有找到的目標文件都加載到可執(zhí)行文件中,但是千萬不要隨便使用這個參數(shù)!假如你使用了不止一個靜態(tài)庫文件,然后又使用了這個參數(shù),那么你很有可能會遇到ld: duplicate symbol錯誤,因為不同的庫文件里面可能會有相同的目標文件,所以建議在遇到-ObjC失效的情況下使用-force_load參數(shù)。
-force_load:所做的事情跟-all_load其實是一樣的,但是-force_load需要指定要進行全部加載的庫文件的路徑,這樣的話,你就只是完全加載了一個庫文件,不影響其余庫文件的按需加載。
demo :mySDK
demo :myTool
希望大家批評指正。
持續(xù)更新:
1、關(guān)于使用static library的這部分,我們創(chuàng)建的不管是動態(tài)庫還是靜態(tài)庫在工程中的呈現(xiàn)都是靜態(tài)的,因為系統(tǒng)不允許我們使用動態(tài)的庫,我們?yōu)榱藴p少不必要的麻煩直接選擇靜態(tài)的,假如我記得沒有錯,你選擇動態(tài)的在提交之后應該會被拒。
2、關(guān)于上面的使用公共的第三方庫,例如AFNetWorking、SDWebImage等這個第三方庫的問題,我在上面寫到過,我們可以將所有的名字、協(xié)議修改,但是工作量有點巨大,而且容易出錯,那我們就可以將這些公用的庫放在外面,打成SDK之后讓別人引用sdk的時候去引入這些第三方框架,這是可以的,我已經(jīng)做過實踐了。