在设计SDK或其它框架的时候,我们大多数不想将自己的源码暴露给别人。在iOS中可行的办法有静态库和静态Framework(虽然苹果今年来开放了动态Framework,但是目前项目中用的最多还是前面两种)。最开始在Xcode中制作静态Framework是很麻烦的,制作静态库要简单的多,毕竟Xcode带了这样一选项。但是使用Xcode6或7的时候发现自带了和静态库制作一样的功能,因此制作动态库已经很简单了。
1. Framework静态库
其中最重要的设置就是将Mach-O Type设为Static Library,因为默认创建Framework的时候是动态库Dynamic Library类型。
步骤
a. 点击创建Framework选项
b. 设置Mach-O Type为Static Library。(如果要支持bitcode, 还需要在TAGETS的Build setting中搜索Other C Flags,添加命令“-fembed-bitcode”)。
如果没有加cflags可能在使用的时候出现以下错误:
c. 设置头文件类型。(Public(公共的),这里存放供其他人查看的header。Private(私有的)这里存放私有的Header,以上两个Headers存放位置都会暴露出来,所有人可以查看。有些Header是不想给别人看到的。这种header放在第三个类Project中。设置的时候直接将工程中的头文件拖到对应的区域)。如果你用了Category,别人在用你的Framework时会发生崩溃。这时别人在引用时需要在工程中other linker flags中添加-objC如果依然有问题,再添加-all_load。
d.选择通用iOS设备build
如果需要制作模拟器和真机通用版本的可以使用shell脚本在命令行构建,也可以在Xcode新建个build的target,添加构建脚本。
a. 新建target
b. 为target添加Run Script,这样就可以在项目工程文件的Products目录生成通用的静态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. 查看库的架构可以通过file或者lipo命令来查看
在使用的时候(链接状态为Required),我们通过MachOView查看可执行文件,并不能看见动态链接了我们的FrameWork,因为静态库编译进了二进制可执行文件中,并不需要动态链接。
2. Framework动态库
默认情况下如果我们不强制设置Mach-O Type为Static Library,那么编译出来Framework就是动态库。
a. 使用Xcode自动链接动态Framework。此时我们只需要在Embedded Binaries中添加需要使用的动态Framework,如下图:
添加之后在Build Phases中会多出一项Embed Fraworks,它的作用也就是拷贝动态库到Runpath Search Paths目录。
至于app运行的时候如何找到动态库,我们可以设置Runpath Search Paths路径。默认会在@executable_path/Frameworks目录中找,@executable_path/表示可执行文件所在路径,即沙盒中的.app目录,注意不要漏掉最后的/。
编译好,进入到生成的.app文件的根目录,发现Framework已经拷贝到了Frameworks目录(Runpath Search Paths路径)。
通过MachOView查看可执行文件,这时候发现程序需要动态链接我们刚创建的Framework。
在使用的过程中如果出现ld: warning: embedded dylibs/frameworks only run on iOS 8 or later警告,这是因为工程默认编译设置的是Dynamic Framework。这种编译只有在iOS8以后才能使用,因此需要设置工程最低支持iOS8.0
b. dlopen或NSBundle加载动态库
动态库中真正的可执行代码为KGSDK.framework/KGSDK文件,因此使用dlopen时如果仅仅指定加载动态库的路径为KGSDK.framework是没法成功加载的。使用时将动态库传到特定目录,然后手动加载,最后用runtime来执行相关操作。
- (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);
}
}