在設(shè)計(jì)SDK或其它框架的時(shí)候峡碉,我們大多數(shù)不想將自己的源碼暴露給別人抄课。在iOS中可行的辦法有靜態(tài)庫和靜態(tài)Framework(雖然蘋果今年來開放了動(dòng)態(tài)Framework归形,但是目前項(xiàng)目中用的最多還是前面兩種)糯景。最開始在Xcode中制作靜態(tài)Framework是很麻煩的拴泌,制作靜態(tài)庫要簡單的多贱案,畢竟Xcode帶了這樣一選項(xiàng)肛炮。但是使用Xcode6或7的時(shí)候發(fā)現(xiàn)自帶了和靜態(tài)庫制作一樣的功能,因此制作動(dòng)態(tài)庫已經(jīng)很簡單了宝踪。
1. Framework靜態(tài)庫
其中最重要的設(shè)置就是將Mach-O Type設(shè)為Static Library侨糟,因?yàn)槟J(rèn)創(chuàng)建Framework的時(shí)候是動(dòng)態(tài)庫Dynamic Library類型。
步驟
a. 點(diǎn)擊創(chuàng)建Framework選項(xiàng)
b. 設(shè)置Mach-O Type為Static Library瘩燥。(如果要支持bitcode秕重, 還需要在TAGETS的Build setting中搜索Other C Flags,添加命令“-fembed-bitcode”)厉膀。
如果沒有加cflags可能在使用的時(shí)候出現(xiàn)以下錯(cuò)誤:
c. 設(shè)置頭文件類型溶耘。(Public(公共的),這里存放供其他人查看的header服鹅。Private(私有的)這里存放私有的Header凳兵,以上兩個(gè)Headers存放位置都會(huì)暴露出來,所有人可以查看企软。有些Header是不想給別人看到的庐扫。這種header放在第三個(gè)類Project中。設(shè)置的時(shí)候直接將工程中的頭文件拖到對(duì)應(yīng)的區(qū)域)仗哨。如果你用了Category形庭,別人在用你的Framework時(shí)會(huì)發(fā)生崩潰。這時(shí)別人在引用時(shí)需要在工程中other linker flags中添加-objC如果依然有問題厌漂,再添加-all_load萨醒。
d.選擇通用iOS設(shè)備build
如果需要制作模擬器和真機(jī)通用版本的可以使用shell腳本在命令行構(gòu)建,也可以在Xcode新建個(gè)build的target桩卵,添加構(gòu)建腳本验靡。
a. 新建target
b. 為target添加Run Script,這樣就可以在項(xiàng)目工程文件的Products目錄生成通用的靜態(tài)FrameWork雏节。
# Sets the target folders and the final framework product.
# 如果工程名稱和Framework的Target名稱不一樣的話胜嗓,要自定義FMKNAME
# 例如: FMK_NAME = "MyFramework"
FMK_NAME=${PROJECT_NAME}
# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework
# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework
# -configuration ${CONFIGURATION}
# Clean and Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos -arch armv7 -arch armv7s -arch arm64 clean build
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator -arch x86_64 clean build
# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
# Uses the Lipo Tool to merge both binary files
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
rm -r "${WRK_DIR}"
open "${SRCROOT}/Products/"
echo ${FMK_NAME}
c. 查看庫的架構(gòu)可以通過file或者lipo命令來查看
在使用的時(shí)候(鏈接狀態(tài)為Required),我們通過MachOView查看可執(zhí)行文件钩乍,并不能看見動(dòng)態(tài)鏈接了我們的FrameWork辞州,因?yàn)殪o態(tài)庫編譯進(jìn)了二進(jìn)制可執(zhí)行文件中,并不需要?jiǎng)討B(tài)鏈接寥粹。
2. Framework動(dòng)態(tài)庫
默認(rèn)情況下如果我們不強(qiáng)制設(shè)置Mach-O Type為Static Library变过,那么編譯出來Framework就是動(dòng)態(tài)庫埃元。
a. 使用Xcode自動(dòng)鏈接動(dòng)態(tài)Framework。此時(shí)我們只需要在Embedded Binaries中添加需要使用的動(dòng)態(tài)Framework媚狰,如下圖:
添加之后在Build Phases中會(huì)多出一項(xiàng)Embed Fraworks岛杀,它的作用也就是拷貝動(dòng)態(tài)庫到Runpath Search Paths目錄。
至于app運(yùn)行的時(shí)候如何找到動(dòng)態(tài)庫崭孤,我們可以設(shè)置Runpath Search Paths路徑类嗤。默認(rèn)會(huì)在@executable_path/Frameworks目錄中找,@executable_path/表示可執(zhí)行文件所在路徑辨宠,即沙盒中的.app目錄遗锣,注意不要漏掉最后的/。
編譯好嗤形,進(jìn)入到生成的.app文件的根目錄精偿,發(fā)現(xiàn)Framework已經(jīng)拷貝到了Frameworks目錄(Runpath Search Paths路徑)。
通過MachOView查看可執(zhí)行文件赋兵,這時(shí)候發(fā)現(xiàn)程序需要?jiǎng)討B(tài)鏈接我們剛創(chuàng)建的Framework笔咽。
在使用的過程中如果出現(xiàn)ld: warning: embedded dylibs/frameworks only run on iOS 8 or later警告,這是因?yàn)楣こ棠J(rèn)編譯設(shè)置的是Dynamic Framework毡惜。這種編譯只有在iOS8以后才能使用拓轻,因此需要設(shè)置工程最低支持iOS8.0
b. dlopen或NSBundle加載動(dòng)態(tài)庫
動(dòng)態(tài)庫中真正的可執(zhí)行代碼為KGSDK.framework/KGSDK文件斯撮,因此使用dlopen時(shí)如果僅僅指定加載動(dòng)態(tài)庫的路徑為KGSDK.framework是沒法成功加載的经伙。使用時(shí)將動(dòng)態(tài)庫傳到特定目錄,然后手動(dòng)加載勿锅,最后用runtime來執(zhí)行相關(guān)操作帕膜。
- (IBAction)clickButton:(id)sender
{
NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/KGSDK.framework/KGSDK",NSHomeDirectory()];
[self openDylibWithPath:documentsPath];
}
- (void)openDylibWithPath:(NSString *)path
{
void* libHandle = NULL;
libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
if (libHandle == NULL) {
char *error = dlerror();
NSLog(@"dlopen error: %s", error);
} else {
NSLog(@"dlopen load framework success.");
}
}
- (void)openDylib
{
NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/KGSDK.framework",NSHomeDirectory()];
NSError *err = nil;
NSBundle *bundle = [NSBundle bundleWithPath:documentsPath];
if ([bundle loadAndReturnError:&err]) {
NSLog(@"bundle load framework success.");
} else {
NSLog(@"bundle load framework err:%@",err);
}
}