iOS開發-動態和靜態FrameWork


開發中我們會使用到第三方的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就不用再添加了,如果已經添加建議刪除~

參考鏈接:https://insert.io/frameworkios8xcode6/

http://www.cnblogs.com/xiaofeixiang/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM