開發中我們會使用到第三方的SDK,有的時候也會將整個系統的公用的功能的抽象出來成為FrameWork,我們只需要暴露對外的接口,使用者只需要調用接口,對於內部實現的過程不需要維護,可以以庫的形式進行封裝,只暴露出頭文件。庫(FrameWork)是編譯好的二進制文件,編譯的時候只需要 Link 一下,提高浪費編譯時間,庫分為靜態庫和動態庫。
基礎知識
靜態庫即靜態鏈接庫(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做靜態,是因為靜態庫在編譯的時候會被直接拷貝一份,復制到目標程序里,這段代碼在目標程序里就不會再改變了。靜態庫的好處很明顯,編譯完成之后,庫文件實際上就沒有作用了。目標程序沒有外部依賴,直接就可以運行。當然其缺點也很明顯,就是會使用目標程序的體積增大。
動態庫動態庫即動態鏈接庫(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib)。與靜態庫相反,動態庫在編譯時並不會被拷貝到目標程序中,目標程序中只會存儲指向動態庫的引用。等到程序運行時,動態庫才會被真正加載進來。動態庫的優點是,不需要拷貝到目標程序中,不會影響目標程序的體積,而且同一份庫可以被多個程序使用(因為這個原因,動態庫也被稱作共享庫)。同時,編譯時才載入的特性,也可以讓我們隨時對庫進行替換,而不需要重新編譯代碼。動態庫帶來的問題主要是,動態載入會帶來一部分性能損失,使用動態庫也會使得程序依賴於外部環境。如果環境缺少動態庫或者庫的版本不正確,就會導致程序無法運行(Linux 下喜聞樂見的 lib not found 錯誤)。
動態庫
xCode6之后制作動態庫相比之前簡單很多,xCode7基本上沿襲了xCode6的操作,細節方面有差別。在 iOS 8 之前,iOS 平台不支持使用動態 Framework,開發者可以使用的 Framework 只有蘋果基礎的 UIKit.Framework,Foundation.Framework 等。iOS 8/Xcode 6 推出之后,iOS 平台添加了動態庫的支持,同時 Xcode 6 也原生自帶了 Framework 支持(動態和靜態都可以),iOS8多了 Extension ,Extension 和 App 是兩個分開的可執行文件,同時需要共享代碼,這種情況下動態庫的支持就是必不可少的了。但是這種動態 Framework 和系統的 UIKit.Framework 還是有很大區別。系統的 Framework 不需要拷貝到目標程序中,我們自己做出來的 Framework 哪怕是動態的,最后也還是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的),因此蘋果又把這種 Framework 稱為Embedded FrameWork.
1.新建動態庫File→New→Target→Cocoa Touch FrameWork:
2.項目名稱DynamicLibrary,同時我們新建兩個測試文件FEUIImage,TestImage:
3.在Dynamic.h中導入頭文件:
#import <UIKit/UIKit.h> //! Project version number for DynamicLibrary. FOUNDATION_EXPORT double DynamicLibraryVersionNumber; //! Project version string for DynamicLibrary. FOUNDATION_EXPORT const unsigned char DynamicLibraryVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import <DynamicLibrary/PublicHeader.h> #import <DynamicLibrary/FEUIImage.h>
4.將FEUIImage和TestImage設置為Public供外部訪問:
5.cmb+b編譯項目,編輯成功之后將項目移動到項目中進行測試:
6.通過FEDevice項目找到編譯之后的DynamicLibrary.framwork文件
通用動態庫
我們將上面的DynamicLibrary.framework移動的其他項目中是可以直接使用的,但是運行的時候會出錯, 錯誤信息如下:
Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_FEUIImage", referenced from: objc-class-ref in ViewController.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
錯誤信息跟動態的指令集和目標項目的指令集版本有關系,我們可以簡單的了解下Achitectures和設備之間的關系,iPhone一直以來都是Arm處理器,Arm是處理器是移動設備上占用率最大的處理器。 在iOS模擬器上運行的是x86指令集,只有在真機上才會執行arm指令集,每個指令集對應不同的機型設置:
都是arm處理器的指令集。通常指令是向下兼容的。在模擬器運行時,iOS模擬器運行的是x86指令集。只有在真機上,才會對執行arm指令集。
armv6:iPhone,iPhone 2G/3G,iPod 1G/2G,xCode4.5已經不支持armv6指令集;
armv7 :iPhone 3GS,iPhone4,iPhone 4s,iPad,iPad2,iPad3(The New iPad),iPad mini,iPod Touch 3G,iPod Touch4,由於iPhone4s的占有率,目前是指令集的最低版本;
armv7s:iPhone5, iPhone5C,iPad4和iPod5;
arm64:iPhone5s,iPhone6,iPhone6 Plus,iPad Air,iPad mini2(iPad mini with Retina Display),iPhone6s,iPhone6s Plus
我們可以通過lipo命令查看發現i386是mac版指令集:
lipo -info DynamicLibrary.framework/DynamicLibrary Non-fat file: DynamicLibrary.framework/DynamicLibrary is architecture: i386
1.如果用真機調試的話,同樣會發生程序錯誤,所以需要將同樣的代碼同時支持模擬器和真機,兩份庫聚合一下,回到DynamicLibrary.frameWork項目,通過File→New→Target→Other→Aggregate:
2.執行編譯腳本地址,先選中DynamicLibrary-Universal,添加腳本地址:
/${PROJECT_DIR}/DynamicLibrary/ios-framework-universal-script.sh
3.設置腳本內容,同時設置DynamicLibrary-Universal的依賴項(Target Dependencies)為DynamicLibrary:
#!/bin/sh # ios-build-framework-script.sh # DynamicLibrary 博客園-FlyElephant # 博客園:http://www.cnblogs.com/xiaofeixiang/ # Created by keso on 16/1/17. # Copyright © 2016年 FlyElephant. All rights reserved. set -e set +u ### Avoid recursively calling this script. if [[ $UF_MASTER_SCRIPT_RUNNING ]] then exit 0 fi set -u export UF_MASTER_SCRIPT_RUNNING=1 ### Constants. UF_TARGET_NAME=${PROJECT_NAME} FRAMEWORK_VERSION="A" UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos IPHONE_SIMULATOR_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator ### Functions ## List files in the specified directory, storing to the specified array. # # @param $1 The path to list # @param $2 The name of the array to fill # ## list_files () { filelist=$(ls "$1") while read line do eval "$2[\${#$2[*]}]=\"\$line\"" done <<< "$filelist" } ### Take build target. if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]] then SF_SDK_PLATFORM=${BASH_REMATCH[1]} # "iphoneos" or "iphonesimulator". else echo "Could not find platform name from SDK_NAME: $SDK_NAME" exit 1 fi ### Build simulator platform. (i386, x86_64) echo "========== Build Simulator Platform ==========" echo "===== Build Simulator Platform: i386 =====" xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/i386" SYMROOT="${SYMROOT}" ARCHS='i386' VALID_ARCHS='i386' $ACTION echo "===== Build Simulator Platform: x86_64 =====" xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/x86_64" SYMROOT="${SYMROOT}" ARCHS='x86_64' VALID_ARCHS='x86_64' $ACTION ### Build device platform. (armv7, arm64) echo "========== Build Device Platform ==========" echo "===== Build Device Platform: armv7 =====" xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION echo "===== Build Device Platform: arm64 =====" xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION ### Build device platform. (arm64, armv7) echo "========== Build Universal Platform ==========" ## Copy the framework structure to the universal folder (clean it first). rm -rf "${UNIVERSAL_OUTPUTFOLDER}" mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" ## Copy the last product files of xcodebuild command. cp -R "${IPHONE_DEVICE_BUILD_DIR}/arm64/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" ### Smash them together to combine all architectures. lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/i386/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/x86_64/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" ### Create standard structure for framework. # # If we don't have "Info.plist -> Versions/Current/Resources/Info.plist", we may get error when use this framework. # # MyFramework.framework # |-- MyFramework -> Versions/Current/MyFramework # |-- Headers -> Versions/Current/Headers # |-- Resources -> Versions/Current/Resources # |-- Info.plist -> Versions/Current/Resources/Info.plist # `-- Versions # |-- A # | |-- MyFramework # | |-- Headers # | | `-- MyFramework.h # | `-- Resources # | |-- Info.plist # | |-- MyViewController.nib # | `-- en.lproj # | `-- InfoPlist.strings # `-- Current -> A # echo "========== Create Standard Structure ==========" mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Headers" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" declare -a UF_FILE_LIST list_files "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/" UF_FILE_LIST for file_name in "${UF_FILE_LIST[@]}" do if [[ "${file_name}" == "Info.plist" ]] || [[ "${file_name}" =~ .*\.lproj$ ]] then mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${file_name}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources/" fi done mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" ln -sfh "Versions/Current/Resources/Info.plist" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Info.plist" ln -sfh "${FRAMEWORK_VERSION}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/Current" ln -sfh "Versions/Current/${PROJECT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" ln -sfh "Versions/Current/Headers" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Headers" ln -sfh "Versions/Current/Resources" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" ### Open the universal folder. open "${UNIVERSAL_OUTPUTFOLDER}"
上面的腳本內容在腳本執行的過程中,會依次編譯出支持 i386、x86_64、arm64、armv7、armv7s 的包,然后把各個包中的庫文件通過 lipo 工具合並為一個支持各平台的通用庫文件,再基於最后一個 xcodebuild 命令打出的包的結構(arm64/DynamicLibrary.framework)和這個通用庫文件生成一個支持各個平台的通用 Framwork;最后編譯之后會彈出framework的位置:
4.設置了通用庫之后,還需要在Genral下Embedded Binaries添加一下動態庫:
靜態庫
動態 Framework 是開發中優先考慮的代碼打包方式,但是為了兼容一些低版本系統對動態庫的限制,我們需要打包靜態庫來使用,實現起來比較簡單,在原有的DynamicLibrary項目 Build Settings 下設置 Mach-O Type 值為 Static Library,可以編譯出靜態庫。
設置為靜態庫之后動態庫的最后一步Embedded Binaries就不用再添加了,如果已經添加建議刪除~