iOS库 .a与.framework区别

一、什么是库?

库是共享程序代码的方式,一般分为静态库和动态库。

二、静态库与动态库的区别?

静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

三、iOS里静态库形式?

.a和.framework

四、iOS里动态库形式?

.dylib和.framework

五、framework为什么既是静态库又是动态库?

系统的.framework是动态库,我们自己建立的.framework是静态库。

六、a与.framework有什么区别?

.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。

.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。

.a + .h + sourceFile = .framework。

建议用.framework.

七、为什么要使用静态库?

方便共享代码,便于合理使用。

实现iOS程序的模块化。可以把固定的业务模块化成静态库。

和别人分享你的代码库,但不想让别人看到你代码的实现。

开发第三方sdk的需要。

八、制作静态库时的几点注意:

1 注意理解:无论是.a静态库还.framework静态库,我们需要的都是二进制文件+.h+其它资源文件的形式,不同的是,.a本身就是二进制文件,需要我们自己配上.h和其它文件才能使用,而.framework本身已经包含了.h和其它文件,可以直接使用。

2 图片资源的处理:两种静态库,一般都是把图片文件单独的放在一个.bundle文件中,一般.bundle的名字和.a或.framework的名字相同。.bundle文件很好弄,新建一个文件夹,把它改名为.bundle就可以了,右键,显示包内容可以向其中添加图片资源。

3 category是我们实际开发项目中经常用到的,把category打成静态库是没有问题的,但是在用这个静态库的工程中,调用category中的方法时会有找不到该方法的运行时错误(selector not recognized),解决办法是:在使用静态库的工程中配置other linker flags的值为-ObjC。

4 如果一个静态库很复杂,需要暴露的.h比较多的话,就可以在静态库的内部创建一个.h文件(一般这个.h文件的名字和静态库的名字相同),然后把所有需要暴露出来的.h文件都集中放在这个.h文件中,而那些原本需要暴露的.h都不需要再暴露了,只需要把.h暴露出来就可以了。



iOS开发过程中,有时候会用到第三方的静态库(.a文件),然后导入后发现编译正常但运行时会出现selector not recognized的错误,从而导致app闪退。接着仔细阅读库文件的说明文档,你可能会在文档中发现诸如在Other Linker Flags中加入-ObjC或者-all_load这样的解决方法。

那么,Other Linker Flags到底是用来干什么的呢?还有-ObjC-all_load到底发挥了什么作用呢?

链接器

首先,要说明一下Other Linker Flags到底是用来干嘛的。说白了,就是ld命令除了默认参数外的其他参数。ld命令实现的是链接器的工作,详细说明可以在终端man ld查看。

如果有人不清楚链接器是什么东西的话,我可以作个简单的说明。

一个程序从简单易读的代码到可执行文件往往要经历以下步骤:

源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件

源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。

可能我描述的比较肤浅,因为我自己了解的也不是很深,建议大家读一下这篇文章,可以对链接器做的事情有个大概的了解:链接器做了什么

为什么会闪退

苹果官方Q&A上有这么一段话:

The "selector not recognized" runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.

翻译过来,大概意思就是Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。

解决方法

解决方法在背景那块我就提到了,就是在Other Linker Flags里加上所需的参数,用到的参数一般有以下3个:

  • -ObjC
  • -all_load
  • -force_load

下面来说说每个参数存在的意义和具体做的事情。

首先是-ObjC,一般这个参数足够解决前面提到的问题,苹果官方说明如下:

This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.

简单说来,加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中,虽然这样可能会因为加载了很多不必要的文件而导致可执行文件变大,但是这个参数很好地解决了我们所遇到的问题。但是事实真的是这样的吗?

如果-ObjC参数真的这么有效,那么事情就会简单多了。

Important: For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -allload or -forceload flags.

当静态库中只有分类而没有类的时候,-ObjC参数就会失效了。这时候,就需要使用-all_load或者-force_load了。

-all_load会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。

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




一、framework和.a两种静态库的介绍及区别

.a是一个纯二进制文件,.framework中除了有二进制文件之外还有资源文件。

.a文件不能直接使用,至少要有.h文件配合,.framework文件可以直接使用。

.a + .h + sourceFile = .framework。

.a只是静态库。framework既可以是静态库也可以是动态库。例如系统的framework就是动态库。

静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。

然而苹果是不会让开发者有自己的动态库的。

二、framework制作及使用

1.新建framework项目

2.加一些我们实用的类进来

3.文件刚拉进来时如下图,只有APPBaseSDK.h是默认放在public中,我们还要把project中需要暴露给外面用的.h文件移到public中去

4.然后设置编译模式,打开Xcode菜单Product--->Scheme--->Edit Scheme,改为release模式,因为最终打包是要用release模式

5.设置最低支持版本

6.设置编译出的静态库包含的指令集

模拟器:iPhone4s~5 : i386 iPhone5s~6plus : x86_64
真机:iPhone3gs~4s : armv7 iPhone5~5c : armv7s iPhone5s~6plus : arm64
如果Build Active Architecture Only设置为YES,那么编译出来的静态库就只包含当前设备的指令集。
举个例子:如果我们选择iPhone 5模拟器编译,则编译出来的静态库只能用iPhone4s~5模拟器跑程序,用iPhone5s~6plus,则会报找不到x86_64的APPBaseSDK库。
设置为NO,则会把所有指令集的都打包合并。因此静态库有个缺点就是静态库包比源码大很多。

7.最后修改生成的Mach-O格式

8.编译生成静态库

编译时,需要用模拟器和真机各编译一次,这样Products目录下的APPBaseSDK.framework静态库才会变为黑色,右键show in Finder,可以进入Products目录下。

9.合并静态库文件

要让真机和模拟器都可用该静态库,需要将两种静态库合并。framework静态库合并的不是framework,而是framework下的一个二进制文件,即上图中标记的待合并文件。lipo -create 第一个framework下二进制文件的绝对路径 第二个framework下二进制文件的绝对路径 -output 最终生成合并的二进制文件路径(我把它放桌面上)。
打开终端使用的命令如下:

lipo -create /Users/zhanglinfeng/Library/Developer/Xcode/DerivedData/APPBaseSDK-dpqdspcdgwsrxgdihiaxpqpkvali/Build/Products/Release-iphoneos/APPBaseSDK.framework/APPBaseSDK /Users/zhanglinfeng/Library/Developer/Xcode/DerivedData/APPBaseSDK-dpqdspcdgwsrxgdihiaxpqpkvali/Build/Products/Release-iphonesimulator/APPBaseSDK.framework/APPBaseSDK -output /Users/zhanglinfeng/Desktop/APPBaseSDK

如果觉得敲文件路径好麻烦,可以将该文件拖入终端即键入了该文件的路径。如果报错建议将上图中Release-iphoneos和Release-iphonesimulator中的APPBaseSDK.framework删掉,重新用模拟器和真机分别编译一次再试。

真机的或者模拟器中随便选一个framework,将该framework中的二进制文件APPBaseSDK(也就是上图中待合并的文件)替换成刚刚合并的文件(刚生成在桌面上的)。好了,此framework就是我们需要的。

10.使用framework

将framework拖入新建的一个UseFrameworkTest工程,我这里拖到下图箭头所指的UseFrameworkTest文件夹目录下,如果你要拖到其他目录,就要改Library Search Paths,后面我会介绍Library Search Paths路径相关知识

将framework中比较常用的头文件import到APPBaseSDK.h中(如下图画圈的),这样在外面只需要#import <APPBaseSDK/APPBaseSDK.h>,就可以引用到下图中画圈的文件。如果不想将太多文件都import到APPBaseSDK.h中,在外面就像这样引用#import <APPBaseSDK/MBProgressHUD+Easy.h>(虽然能使用但会报警告,所以还是import到APPBaseSDK.h中吧)

使用代码,如下图可以正常调用将汉字转拼音方法并打印结果了

注意:如果要用到framework中的category方法,需要设置Other Linker Flags为-ObjC(注意大小写,有些资料里大小写搞错了坑死我了)。引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来本来这样就可以解决问题了,不过在64位的Mac系统或者iOS系统下,链接器有一个 bug,会导致只包含有类别的静态库无法使用-ObjC标志来加载文件。变通方法是使用-all_load 或者-force_load标志,它们的作用都是加载静态库中所有文件,不过all_load作用于所有的库,而-force_load后面必须要指定具 体的文件。

 

三、.a静态库的制作及使用

1.创建静态库工程,工程命名为BaseSDK,生成的.a文件名变成libBaseSDK。

2.删掉自动生成的文件BaseSDK.h   BaseSDK.m

3.添加你的实用类文件

4.添加Headers Phase

5.将暴露给外面用的头文件加入进来,加进来后要移到public中去(不移到public中也没错,只是下面第11步中不会出现.h文件,需要从库的源码中找)

6.然后设置编译模式,打开Xcode菜单Product--->Scheme--->Edit Scheme,改为release模式,因为最终打包是要用release模式

 

7.设置Build Active Architecture Only

模拟器:iPhone4s~5 : i386 iPhone5s~6plus : x86_64
真机:iPhone3gs~4s : armv7 iPhone5~5c : armv7s iPhone5s~6plus : arm64
如果Build Active Architecture Only设置为YES,那么编译出来的.a静态库就只包含当前设备的指令集。
举个例子:如果我们选择iPhone 5模拟器编译,则编译出来的.a静态库只能用iPhone4s~5模拟器跑程序,用iPhone5s~6plus,则会报找不到x86_64的APPBaseSDK库。
设置为NO,则会把所有指令集的都打包合并。因此静态库有个缺点就是静态库包比源码大很多。

8.设置最低支持版本

9.编译生成静态库

编译时,需要用模拟器和真机各编译一次,这样Products目录下的libBaseSDK.a静态库才会变为黑色,右键show in Finder,可以进入Products目录下。

10.合并模拟器和真机静态库文件libBaseSDK.a,打开终端命令如下

lipo -create /Users/zhanglinfeng/Library/Developer/Xcode/DerivedData/BaseSDK-cexmrzesjuswutaldkedwjpnpxnk/Build/Products/Release-iphoneos/libBaseSDK.a /Users/zhanglinfeng/Library/Developer/Xcode/DerivedData/BaseSDK-cexmrzesjuswutaldkedwjpnpxnk/Build/Products/Release-iphonesimulator/libBaseSDK.a -output /Users/zhanglinfeng/Desktop/libBaseSDK.a

11.使用.a静态库

将静态库拖入新建的工程,我这里拖到下图箭头所指的UseA文件夹下(如果你要拖到其他目录,就要改Library Search Paths,后面我会介绍Library Search Paths路径相关知识),再将暴露给外面用的.h文件也拖入工程,

注意:如果没有include里的.h文件.那就从库的源码中挑出一些需要暴露的.h文件。

导入头文件就可以使用了,如下图

注意:如果要用到静态库中的category方法,需要设置Other Linker Flags为-ObjC(注 意大小写,有些资料里大小写搞错了坑死我了)。引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来本来这样就可以解决问题 了,不过在64位的Mac系统或者iOS系统下,链接器有一个 bug,会导致只包含有类别的静态库无法使用-ObjC标志来加载文件。变通方法是使用-all_load 或者-force_load标志,它们的作用都是加载静态库中所有文件,不过all_load作用于所有的库,而-force_load后面必须要指定具 体的文件。

四、关于使用库时Library Search Paths

 也许有人会纳闷不用设置search Paths吗,如果你按照我的步骤把静态库拖到工程的相应目录,那就默认设置就可以了。

如果你把静态库放到你喜欢的路径,就要根据静态库在文件夹中的位置设置这个search Paths了。

下图标明了文件夹路径

“./”和“$(PROJECT_DIR)”表示当前工程所在文件夹,是一个相对路径,相对该工程在电脑的位置,会自动定位到在当前电脑的绝对路径。如果写绝对路径,工程换了文件夹位置,或换到其他电脑,路径就报错。“../”表示当前工程所在文件夹的上一层文件夹路径。设置路径时两个参数的意义,non-recursive非递归查找,recursive 递归查找 。


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值