Xcode 的Framework和.a静态库的制作

2015-12-01 | 阅读

Xcode静态库build后,依旧为红色,无法找到构建结果

这是Xcode的BUG,要将编译模式改为Device,然后编译成功就可以看到.a文件了.切换为模拟器,再编译。也可以由之前的真机.a文件路径找到模拟器的文件.

合并真机包和模拟器包

构建Framework时,分为真机版本和模拟器版本,要将两者合并,新建一个Target - Aggregate,然后在Target的Build Phases中点加号,添加Run Script,然后添加类似如下的Shell :

# 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 clean build
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator 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 (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}"
rm -r "${WRK_DIR}"

Xcode中的常用环境变量

  • $(BUILT_PRODUCTS_DIR) : build成功后,最终产品路径.
  • $(TARGET_NAME) : 目标工程名称
  • $(SRCROOT) : 工程文件的路径
  • $(CURRENT_PROJECT_VERSION) : 当前工程版本号

静态库中的category

如果在Framework内有用到category,需要在使用项目中在选项Build SettingsLinkingother Linker Flags 下 ,添加 –ObjC. 这样,Framework中的category同样可以由项目中的类使用,而项目中的category同样会影响Framework中的类.

创建Framework,实际还是静态库,需要在在Mach-O-type 选择静态库 static Library . 否则加入应用无法进行连接,而如果不添加该标记,直接加入应用,有两种方法可以拯救, 在 General – Embedded binaries下添加新加的动态库.或在Build Phases 下添加copy file phase, 然后再copy file中添加该动态库.不然就会出错误Library not loaded image not found.

workspace

Workspace中的Framework联编,对于同一个Workspace的项目,可以引用其他项目的Framework或者.a文件到项目中,在General - Linked Frameworks and Libraries中添加workspace中的项目.添加后,项目中就会出现真机版本的Framework,但是如果你使用模拟器构建,它会自动连接模拟器版本的Framework,但是项目中只会显示真机版本的Framework.

iPhone设备的架构类型

使用lipo –info来输出静态库的信息. 静态库编译的时候有多个架构,分别为ARM和x86_64. ARM是iphone上得架构,分别有arm64,armv6,armv7,armv7s,分别对应以下设备:

  • armv6 : iPhone3G , 第一代和第二代iPod Touch

  • armv7 : iPhone4 , iPhone4S

  • armv7s : iPhone5 , iPhone5C

  • arm64 : iPhone5S , iPhone6,iPhone7

指令集都是向下兼容的.

  • Architecture : 指你想支持的指令集。
  • Valid architectures : 指即将编译的指令集。
  • Build Active Architecture Only : 只是否只编译当前适用的指令集,即只编译当前连接真机或者模拟器的版本

Framework 加载机制

只有OC的类的类方法被调用时,这个类才会被编译进当前的项目中,否则不会被编译进当前项目,除非使用标签-ObjC或者load.所以直接反射这些未被加载的.m文件所表示的类时,会加载失败.

打包FrameWork的名称

这个问题提出的原因是,如果打包framework要提供不同的包,如根据不同的发布渠道,要有不同的bunldid,所以要有不同的framework包.而我们在一次打包时,肯定是要打出所有的包,而在jenkins上编译项目时,是直接根据当前版本来选择不同的包来进行替换.替换的目标framework的名称肯定是xxx.framework,而我们统一打出所有framework包时,包的名称的设置时遇到这个问题.

一般target的名字定义了FrameWork的名字,framework里面有 codesign,进行验证,如果直接改变打好了的framework包的名字,APP打包Linker时会以这个改名后的framework名字尝试去加载二进制数据,但是framework中的二进制数据确实以别的名字保存的,导致无法正常找到数据.

在打包framework时,直接设置framework名称不是target名,而是自己指定的名称.

查看framework支持的架构

lipo -info openssl.framework/openssl 

打包时设置支持bitcode

添加参数:

ENABLE_BITCODE=YES OTHER_CFLAGS="-fembed-bitcode"

使用动态加载framework来实现热更新

apple不支持动态下发代码,所以这种动态下发framework来实现热更新的机制,过不了审核,但是还是有用处的,用于不用审核的企业内部应用中,即企业证书应用.

加载动态库的方式为:

 //加载方式二:使用NSBundle加载动态库  
    NSBundle *frameworkBundle = [NSBundle bundleWithPath:destLibPath];  
    if (frameworkBundle && [frameworkBundle load]) {  
        NSLog(@"bundle load framework success.");  
    }else {  
        NSLog(@"bundle load framework err:%@",error);  
        return;  
    }  
  
/* 
 *通过NSClassFromString方式读取类 
 *PacteraFramework 为动态库中入口类 
 */  
Class pacteraClass = NSClassFromString(@"PacteraFramework");  
if (!pacteraClass) {  
    NSLog(@"Unable to get TestDylib class");  
    return;  
}  
  
/* 
 *初始化方式采用下面的形式 
  alloc init的形式是行不通的 
  同样,直接使用PacteraFramework类初始化也是不正确的 
 *通过- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; 
  方法调用入口方法(showView:withBundle:),并传递参数(withObject:self withObject:frameworkBundle) 
 */  
NSObject *pacteraObject = [pacteraClass new];  
[pacteraObject performSelector:@selector(showView:withBundle:) withObject:self withObject:frameworkBundle];