iOS 静态库(静态库依赖、三方依赖、资源处理等)


目录


一、 基本概念
     1. 静态库动态库区别
     2. 库的版本
     3. iOS 设备的CPU架构
     4. ARM处理器指令集
     5. i386|x86_64 指令集
     6. Xcode中指令集相关选项
二、打包framewor静态库
     1. 创建项目-》创建静态库项目
     2. 静态库如何开发调试?
     3. 脚本合成静态库
     4. 自己打包的静态库依赖第三方.a和.framework静态库?
     5. 自己打包的静态库依赖第三方框架比如YYModel、AFN?
     6. 静态库依赖的图片、xib等资源处理?
     7. -ObjC / -all_load / -force_load 什么鬼


文章中的demo资源代码可以从这里下载

静态库和动态库

  • 静态库 -> .a或者.framework的库

  • 动态库 -> .dylib或者.framework的库

区别:

  • 静态库在系统中多次使用就有多份拷贝 (比如A应用里有个xxx.a的静态库、B应用里有一个xxx.a的静态库,那么在系统内存里就会占用两份)

  • 动态库是由系统动态加载到内存,而且只加载一次,多个程序共用节省内存

我们自己制作的动态库苹果不允许上架到App Store 所以这里不做介绍 下面主要说一下静态库

库的版本

Debug版本 -》 真机Debug版本 / 模拟器Debug版本

包含完整的符号信息方便调试
不会对代码进行优化

Release版本 -》真机Release版本 / 模拟器Release版本

不包含完整的符号信息
执行代码进行过优化
执行速度会更快些

几个概念

iOS 设备的CPU架构

  • 在模拟器上支持:

      iPhone4s-5:  i386 架构
      iPhone5s-8 Plus: x86_64 架构
    
  • 在真机设备上支持:

      armv6: iPhone、iPhone 2、iPhone 3G、iPod Touch(第一代)、iPod Touch(第二代)
      armv7: iPhone 3Gs、iPhone 4、iPhone 4s、iPad、iPad 2
      armv7s: iPhone 5、iPhone 5c (静态库只要支持了armv7,就可以在armv7s的架构上运行)
      arm64: iPhone 5s、iPhone 6、iPhone 6 Plus、iPhone 6s、iPhone 6s Plus、iPad Air、iPad Air2、iPad mini2、iPad mini3
    

ARM处理器指令集

  • 几乎所有手机处理器都基于ARM处理器的,ARM处理器特点是体积小、低功耗、低成本、高性能,所以在嵌入式系统中应用广泛

      armv6|armv7|armv7s|arm64都是ARM处理器的指令集
      这些指令集都是向下兼容的
    

i386|x86_64 指令集:i386和x86_64 是Mac处理器的指令集

  • i386是针对intel通用微处理器32位处理器的
  • x86_64是针对x86架构的64位处理器

所以当使用iOS模拟器的时候会遇到i386|x86_64(ios模拟器没有arm指令集)

模拟器32位处理器测试需要i386架构。
模拟器64位处理器测试需要x86_64架构。
真机32位处理器需要armv7或者armv7s架构。
真机64位处理器需要arm64架构

命令查看静态库支持的架构:

通过 lipo -info - 》 拖入模拟器或者真机.framework的路径 查看静态库支持的架构

Xcode中指令集相关选项(Build Setting中)

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第1张图片
1.png

1. Architectures

指定工程被编译成支持哪些指令集类型.

支持的指令集越多,就会编译出很多个指令集代码的数据包,对应生成二进制包就越大,也就是ipa包越大.

2. Valid Architectures

限制可能被支持指令集的范围.

xcode编译出来的二进制包类型最终从这些类型产生,而编译出哪些指令集的包,将由Architectures与Valid Architectures这些交集来确定,面会举例说明.

3. Build Active Architecture Only

指定只对当前连接设备所支持的指令集编译.

当设置为YES时是为了debug编译的速度更快,它只会编译当前的architecture版本

当设置为NO时,会编译所有的版本,所以一般debug设置为YES,release设置为NO以适应不同设备.

配置参数举例

  • 例1

      Valid Architectures设置支持arm指令集有:armv7/armv7s/arm64
      Architectures设置的支持arm指令集有:armv7s 
      这时Xcode只会生成一个armv7s指令集的二进制包
    
  • 例2

      Valid Architectures支持:armv7s/arm64
      Architectures支持:armv7/armv7s
      这时Xcode生成二进制包所支持的指令集:armv7s
    
  • 例3:

      ValidArchitectures支持: armv6/armv7s/arm64
      Architectures支持: armv7/armv7s/arm64
      Xcode生成生成二进制包支持的指令集:arm64
    
  • 例4

      Valid Architectures: armv6/armv7s/arm64
      Architectures: armv6/armv7/armv7s
      Xcode生成二进制包支持的指令集:armv7s
    
  • 例5:

      Valid Architectures: armv7/armv7s
      Architectures: armv7/armv7s/arm64
      这种情况是报错的 因为允许使用指令集中没有arm64
    

在Xcode6.1.1里的Valid Architectures 设置默认为 Standard architectures(armv7,arm64),如果想改的话自己在other中更改

结论: 使用 standard architectures (including 64-bit)(armv7,arm64) 参数,则打的包里面有32位、64位两份代码,在iPhone5s(iPhone5s的cpu是64位的)下,会首选运行64位代码包, 其余的iPhone(其余iPhone都是32位的,iPhone5c也是32位),只能运行32位包,但是包含两种架构的代码包,只有运行在ios6以上的系统上

而使用 standard architectures (armv7,armv7s) 参数,则打的包里只有32位代码,iPhone5s的cpu是64位,但是可以兼容32位代码,即可以运行32位代码。但是这会降低iPhone5s的性能。 其余的iPhone对32位代码包更没问题, 而32位代码包,对系统也几乎也没什么限制。

从需求开始

比如现有腾讯广告的.a静态库 和 头条的.framework静态库, 还有我们自己公司的广告处理类, 现在要把这三家广告业务集合在一起打包成一个framwork静态库。

带着疑问开始

  • 我们要制作的framwork静态库 包含其他第三方的framwork静态库怎么办?
  • 我们要制作的framwork静态库 依赖第三方框架 比如AFN、YYModel等怎么办?
  • 我们要制作的framwork静态库 依赖的资源 比如图片、XIB怎么办?
  • 制作好的静态库怎么进行调试?
  1. 创建静态库项目 ZZAdSDK
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第2张图片
image.png

接着添加target ,选择创建Cocoa Touch Framwork


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第3张图片
image.png
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第4张图片
image.png

之所有没有直接选择Cocoa Touch Framwork创建静态库,而是通过target方式创建静态库,是因为涉及到静态库的调试问题

比如我们制作好了一个静态库,然后把它拖入到项目里,然后发现有些问题,但是我们无法进行断点调试。

通过上面这种Target方式创建静态库方式我们是可以直接断点调试的,我们就可以一边写静态库一边进行调试,调试好后编译生成Framwork再拖入项目,发现静态库有问题,我们就可以回项目进行调试。

比如我们添加的target交 ZZAdSDKLib,那么项目里有自动生成如下

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第5张图片
image.png

接着我们需要ZZAdSDKLib进行如下设置

  • 设置Build Active Architecture Only 为No
  • 修改Mach-O Type 为 Static Library

因为Framwork可能是静态库也可能是动态库,所以需要制定下ZZAdSDKLib的Mach-O Type 为 Static Library

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第6张图片
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第7张图片
image.png

接着就可以到ZZAdSDKLib目录下编写我们静态库的功能代码了

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第8张图片
image.png
  • 比如ZZSplashAd是我们自己的开屏广告逻辑处理类
  • ZZSplashGDTAd是腾讯的开屏广告处理类
  • ZZSplashToutiaoAd是头条的开屏广告处理类

接着我们需要把腾讯提供的.a静态库等文件和头条的WMAdSDK.framwrok导入进来

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第9张图片
image.png

注意:

在添加三方Framwork库时不要勾选Add to targets选择
添加三方.a库时需要勾选


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第10张图片
image.png
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第11张图片
image.png

说明:

这里ZZSplashToutiaoAd要依赖头条提供的WMAdSDK.framwrok 静态库
这里有一点需要说明,第三方的framwrok静太库是不能直接打包到我们自己的静态库的,我们自己的静态库依赖头条的framwrok静太库,所有制作好自己静态库后需要把自己的静态库和头条的静态库都提供给使用者。

测试说明:

个人测试过,即使把三方framwrok静态库成功打包到我们自己的framwrok静态库,在外界使用我们自己的静态库时也是会报错的

个人测试,直接把三方的.a静态库打包到我们自己的framwrok静态库,在外界是可以正常使用的 (网上也有说.a不能直接打包到framwrok静态库里,但是我测试是可以的,而且在外界用的也是正常的)

接着进行编译发现,说头条的文件找不到,但是我明明已经导入了WMAdSDK.framwrok了

image.png

如果允许发现报类似这种Category方法找不到问题

-[UIButton setHitTestEdgeInsets:]: unrecognized selector sent to instance 

需要到Other Linder Flags 添加 -ObjC

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第12张图片
image.png

因为三方的静态库依赖了一些系统的库,所以接着需要到ZZAdSDK项目下添加相应的系统库

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第13张图片
image.png

再次运行发现报错,因为虽然在创建的静态库里添加了WMAdSDK.framwrok,但是在上面ZZAdSDK项目里用的时候我们自己的静态库依赖WMAdSDK.framwrok,所以在上面也需要导入WMAdSDK.framwrok


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第14张图片
image.png

在ZZAdSDK项目导入WMAdSDK.framwrok静态库


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第15张图片
image.png
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第16张图片
image.png

接着编译成功,然后到Build Phases-》Header 下把需要暴露的.h 添加到Public下

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第17张图片
image.png

再次编译运行成功


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第18张图片
image.png

至此,我们就可以一边开发静态库一边调试了,调试好之后切换ZZAdSDKLib的Scheme为Release模式

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第19张图片
image.png

然后编译生成Framwork,到Products目录下找到对应的Framwork

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第20张图片
image.png

脚本合成Framework

此时我们打包出的Framework是真机的Framework或者是模拟器的Framework
我们可以手动通过命令合成同时支持真机和模拟器的库,也可以通过脚本自动合成

命令:
lipo -create 模拟器库.framework的路径 真机库路径 -output 合成库的名字

脚本自动合成

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第21张图片
image.png
iOS 静态库(静态库依赖、三方依赖、资源处理等)_第22张图片
image.png

粘贴脚本到Run Script


iOS 静态库(静态库依赖、三方依赖、资源处理等)_第23张图片
image.png

然后编译就自动生成合并的framework库了

以下是脚本文件

PROJECT_NAME='ZZAdSDKLib'
#要build的target名
TARGET_NAME=${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}_Products/"

#创建输出目录,并删除之前的framework文件
mkdir -p "${UNIVERSAL_OUTPUT_FOLDER}"
rm -rf "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework"

#分别编译模拟器和真机的Framework
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

#拷贝framework到univer目录
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_FOLDER}"

#合并framework,输出最终的framework到build目录
lipo -create -output "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"

#删除编译之后生成的无关的配置文件
dir_path="${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]]
then
rm -f "${dir_path}/${file}"
fi
done

#判断build文件夹是否存在,存在则删除
if [ -d "${SRCROOT}/build" ]
then
rm -rf "${SRCROOT}/build"
fi

rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"

#打开合并后的文件夹
open "${UNIVERSAL_OUTPUT_FOLDER}"


关于如果处理静态库里的图片等资源,需要创建资源Bundle,关于如何创建Bundle很简单,参考下这个文章

接着把我们制作好的静态库、和依赖的三方静态库、创建好的资源Bundle拖入我们要用的项目里即可

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第24张图片
image.png

因为.a已经打包进我们的静态库这里只拖入了三方的Framwork库,当然如果有依赖系统的库还需要添加好系统的库

最后说一下加入我们自己的广告类ZZSplashAd里面 依赖第三方框架YYModel

#import "ZZSplashAd.h"
#import 

@interface ZZSplashAd()
@end

@implementation ZZSplashAd

- (void)loadAdAndShowInWindow:(id)window withBottomView:(id)bottomView {
      
}
@end

但是我有不想把“YYModel”打包进我的静态库,此时就需要通过Cocoapods进行管理,我们在podfile文件里添加target然后pod install

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第25张图片
image.png

然后编译ZZAdSDKLib静态库-》Show in finder -》找到生成的libYYModel.a 或者YYModel.framework

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第26张图片
image.png

说明:我在测试过程中 有时候生成的是libYYModel.a 有时候是 YYModel.framework,还没分析出原因,如果有知道的小伙伴欢迎交流。

最后,把我们自己打包的ZZAdSDKLib.framework、第三方依赖的framework、资源bundle、第三方库的framework提供给使用者就可以了

iOS 静态库(静态库依赖、三方依赖、资源处理等)_第27张图片
image.png

说明:

比如我们把制作好的静态库拖到了A项目中使用,此时在A项目中必须要pod YYModel, 如果静态库里依赖和AFN,那么也必须pod AFN

-ObjC / -all_load / -force_load

我们在集成一些第三方静态库时,在集成文档里有时会看到-ObjC ,-all_load,_force_load 这几个东东,这几个设置是什么意义那??

-ObjC

我们知道在Objective-C中方法调用都是在运行期确定的,所以Objective-C没有针对每个方法定义链接符号,它只每个类创建链接符号。因此当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时出现selector not recognized的错误。

设置ObjC标志后,链接器会把一个类相关的所有目标文件都加载进来,这样就解决了这个问题。由于这样做会使可执行文件体积变大,所以需要需要自己手动设置一下。

关于-ObjC苹果的官方解释是这样的

-all_load

在64位ios应用环境下,在静态库中只有category而没有对应的class定义时-ObjC标志会失效(这是链接器的一个bug)
这时可以使用-all_load强制加载所有目标文件 或者使用-force_load指定加载某一个包
千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件
所以建议在遇到-ObjC失效的情况下使用-force_load参数

-force_load

-force_load所做的事情跟-all_load其实是一样的
但是-force_load需要指定要进行全部加载的库文件的路径
这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载


参考文档



xcode基础配置详解之Architecture(一)
-ObjC ,-all_load,_force_load的区别

你可能感兴趣的:(iOS 静态库(静态库依赖、三方依赖、资源处理等))