Framework 动态库 & 静态库

96
曦风兮
2017.10.17 15:36* 字数 3165

关于 动态库 & 静态库 ,参考下面文章:
iOS开发关于"框架"的那些事
iOS 静态库,动态库与 Framework
framework中关于资源的读取
一篇较为详细的 iOS动态静态库创建打包方法 总结


下面这段很有意思,对我有很大启发,就贴了过来

  • 以前的.a静态库是不能包含资源文件的。所以毫无疑问,如果需要xib, img等资源文件的话,需要打包在.bundle中,和.a一起发布,一般保持两者同样的名字。

  • 从iOS8开始,可以用framework,并且可以包含资源文件,可以放弃.bundle文件了。这主要是从使用方便的角度来讲的。将代码和资源分离,完成同一功能要找两个地方,自找麻烦。

  • 在framework中开发,就像跟主程序中开发一样,按照功能分模块,划分文件夹。将同一功能的文件都放在一个文件夹中,包括Storyboard,xib,image,Code等等,以最短路径获取所需要的信息。

  • 将framework和bundle分两个隔离是不合理的,同样,在framework中包含一个bundle同样也是不合理的。模块间隔离的单位是framework,在framework内部,应该考虑充分共享以及获取的方便性,再引入第二级的bundle,只能是自我设限,得不偿失。让.bundle和.a成为历史,全面使用framework。

  • 还有一种情况,就是当资源的体积很大的时候,比如视频、地图之类的。这种时候,将资源单独放在一个bundle中,与程序分离,可以不同时间发布,也不用重新发版本,有一定的意义。不过,对于大资源,直接以文件的形式下载就可以了,有必要用bundle多包一层吗?

  • 除了主程序,其他framework的地位都是平等的,不存在framework之中包含framework的概念。取而代之的,是依赖关系。所以,整体架构在物理上就两层,主程序和framework。逻辑上的层次和包含等概念,都要理解依赖。比如要开发一个Network.framwork,需要用到AFNetworking.framework。在逻辑上是主程序 -> Network.framwork -> AFNetworking.framework。但是在物理上的关系是
    主程序 -> Network.framwork和主程序 ->AFNetworking.framework。AFNetworking.framework与Network.framwork地位是完全平等的,只不过想用Network.framwork的程序必须同时将AFNetworking.framework包含进来。
    可以把framework想象为主程序中的一个文件夹。在没有framework的主程序中,资源是直接放在根目录下的。但是,引入了framework之后,就像把资源移动了相应的子目录。这样路径就发生了变化,NSBundle这个类就是为了区分这种变化。

  • 动态库引用静态库就相当于将代码直接添加在了动态库里

  • 动态库不会被打包进动态库/静态库中,它只会创建一个引用,需要在项目中手动添加被引用的动态库并在工程的 Embedded Binaries 中添加所需的动态库

  • 静态库无法嵌入静态库中。动态库确实是把静态库打在了自己的库中,但是静态库无法做到这一点

framework资源文件读取

1、在framework里面读framwork自己的资源文件

这是framework内部的资源,跟其他都没有关系。但是framework不能单独存在,必须要放在某个“主程序”中才能起作用。bundle参数如果不传,那么默认是mainBundle,这种情况路径就不对了。这种情况下,可以用下面这个API来获得bundle参数。

 // 获取bundle参数

    NSBundle *bundle = [NSBundle bundleForClass:self.class];

    // 读UIStoryboard

    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@“StoryboardName” bundle:bundle];

    // 读UIImage

    UIImage *image = [UIImage imageNamed:@"icon_back_gray" inBundle:bundle compatibleWithTraitCollection:nil];

    // 文件路径

    NSString* htmlPath = [bundle pathForResource:@"index" ofType:@"html"];
2、在主程序中读framework里面的资源文件

同样也是利用bundle参数来读取,class选择framework中某个导出的class就可以了。

// 获取bundle参数,ZAFinanceFrameworkManager是framework中接口类

    NSBundle *bundle = [NSBundle bundleForClass:[ZAFinanceFrameworkManager class]];
3、在framework中读主程序的资源文件

这个和在主程序中读自己的一样,不需要bundle参数,一定要传的话,就传nil或者[NSBundle mainBundle]

4、从framework里面读其他framwork里面的资源文件

同样也是利用bundle参数来读取,class选择目标framework中某个导出的class就可以了。

在单体程序中,NSBundle这个参数不需要管,全部传nil或者是默认的[NSBundle mainBundle]就可以了。
引入了framework之后,就需要NSBundle这个参数来区分资源所在的模块。确定NSBundle比较简单的方法是用下面这个API,其中的class只要选择资源所在的framework中的某个class就可以了。获取到bundle后,通过bundle获取资源文件用法一致。

下面是关于 bundleForClass
https://developer.apple.com/documentation/foundation/nsbundle/1417717-bundleforclass?preferredLanguage=occ
The NSBundle object that dynamically loaded aClass (a loadable bundle), the NSBundle object for the framework in which aClass is defined, or the main bundle object if aClass was not dynamically loaded or is not defined in a framework.
This method creates and returns a new NSBundle object if there is no existing bundle associated with aClass. Otherwise, the existing instance is returned.

+ (NSBundle *)bundleForClass:(Class)aClass;

如何创建 .framework

1、我们先创建一个名为 LovelyCat 的项目

2、在创建一个 LovelyCat 的ViewController ,勾选 Also create XIB file

3、我们往项目中加 加入图片资源文件(可以是文件夹形式也可以是 .xcassets 形式),这里我们项目中加载的是 Resource 文件夹

3、我们可以在 LovelyCat.xib 上放张图片,并给图片加一个点击事件,



4、我们来测试一下,由 rootViewController 模态推出 LovelyCat,完美

5、接下来我们创建一个 Cat target


6、接下来我们要把 framework 需要的代码合资源加入 Cat target,如下图,这里我们把 LovelyCat.h LovelyCat.m LovelyCat.xib 和图片资源都勾选



7、下面我们开始 配置 Cat target 和 外部引用头文件
我们要把 暴露的 .h 文件拖到 Public 下,而且在 Cat target 的 Cat.h 中加入 #import <Cat/LovelyCat.h>

8、关于 framework 所支持的指令集,我们最后单开一个模块来讲。
下面是 Xcode 中指令集相关选项,我们可以先把 Build Active Architecture Only 设为 NO。因为我们一直在用模拟器运行,所以其他的可以先不管

9、这里先说 Linking 中需要设置的选项。
  • Dead Code Stripping 剥离无用的代码,我们这里默认就好, 默认 YES(一般对Debug关闭,对Release版本开启以去除无效路径僵尸代码,压缩安装包体积)
  • Link With Standard Libraries 用标准库连接,默认就好(如果 YES,那么编译器在链接过程中会自动使用通过标准库的链接器)
  • Mach-O Type 可执行文件 类型 默认 Dynamic Library ,我们默认就好 原因看这里iOS开发关于"框架"的那些事
    其他的默认就好,遇到坑了再来填

    10、现在我们可以编译了
    选择个模拟器,真机选择 Generic iOS Device,因为编译指令集不同所以编译后的framework 不能通用

    编译后,我们可以在 Products 找到我们 Cat.framework ,现在我们可以把他放进其他工程中测试了, 我们新建一个 HelloKitty 的工程,把 Cat.framework 拖入 工程中,编译运行 咦,闪退了...
    不着急 我们把 Cat.framework 加入Embeded Binaries 下,编译运行 运行 Ok了,调用Cat.framework 也OK
备注:
  • 如果你用了Category,别人在用你的framework时会发生崩溃。这时在引用时需要在工程中other linker flags中添加-objC如果依然有问题,再添加-all_load。
    不过,我测试了下在 .framework 是动态库 添加 Category,并暴露 Category 的头文件,使用起来是没有问题的,如果是静态库则需要加上上面的方法。
  • 如果有很多关于符号表的警告,这时需要将Generate Debug Symbols设置为NO即可关闭符号表警告。

Apple 移动设备处理器指令集

ARM
arm的意思其实是指处理器的品牌,ARM是英国Acorn有限公司设计的低功耗成本的RISC微处理器。
ARM处理器,特点是体积小、低功耗、低成本、高性能,所以几乎所有手机处理器都基于ARM,在嵌入式系统中应用广泛。

ARM处理器指令集
armv6|armv7|armv7s|arm64都是ARM处理器的指令集,这些指令集都是向下兼容的,例如armv7指令集兼容armv6,只是使用armv6的时候无法发挥出其性能,无法使用armv7的新特性,从而会导致程序执行效率没那么高。
还有两个我们也很熟悉的指令集:i386|x86_64 是Mac处理器的指令集,i386是针对intel通用微处理器32架构的。x86_64是针对x86架构的64位处理器。所以当使用iOS模拟器的时候会遇到i386|x86_64,iOS模拟器没有arm指令集。

iOS 移动设备指令集

armv6 设备: iPhone、 iPhone2、iPhone3G、第一代、第二代 iPod Touch
armv7 设备: iPhone3GS、iPhone4、iPhone4S、iPad、iPad2、iPad3(The New iPad)、iPad mini、iPod Touch 3G、iPod Touch4
armv7s 设备: iPhone5、iPhone5C、iPad4(iPad with Retina Display)
arm64 设备:iPhoneX、iPhone8 plus、iPhone8、iPhone7 plus、iPhone7、iPhone6s、iphone6s plus、iPhone6、iPhone6 plus、iPhone5S、iPad Air、 iPad mini2(iPad mini with Retina Display)
注:
模拟器32位的处理器需要i386的架构,64位的处理器需要x86_64的架构。
真机32位需要arm7,或者arm7s架构,64位需要arm64架构。

Xcode Build Setting中指令集相关选项释义

  • Architectures
    指定工程被编译成可支持哪些指令集类型,而支持的指令集越多,就会编译出包含多个指令集代码的数据包,对应生成二进制包就越大,也就是ipa包会变大。
  • Valid Architectures
    限制可能被支持的指令集的范围,也就是Xcode编译出来的二进制包类型最终从这些类型产生,而编译出哪种指令集的包,将由Architectures与Valid Architectures(因此这个不能为空)的交集来确定。
    比如 Valid Architectures设置的支持arm指令集版本有:armv7/armv7s/arm64,对应的Architectures设置的支持arm指令集版本有:armv7s,这时Xcode只会生成一个armv7s指令集的二进制包。
    再比如:将Architectures支持arm指令集设置为:armv7,armv7s,对应的Valid Architectures的支持的指令集设置为:armv7s,arm64,那么此时,XCode生成二进制包所支持的指令集只有armv7s
  • Build Active Architecture Only
    指定是否只对当前连接设备所支持的指令集编译
    当其值设置为YES,这个属性设置为yes,是为了debug的时候编译速度更快,它只编译当前的architecture版本,而设置为no时,会编译所有的版本。 所以,一般debug的时候可以选择设置为yes,release的时候要改为no,以适应不同设备。
查看 .framework 支持的指令集 和 模拟器库 真机库 合并
  • 使用 lipo -info 查看最终库的信息
lipo -info Cat
Cat 是 framework 包内的 编译后的 exec 文件

可以看到 Debug-iphonesimulator 下的 .framework 支持的指令集是 x86_64, 即 Cat is architecture: x86_64

下面看看真机状态下打包的 .framework 所支持的指令集,没错是 arm64 ,此只能在真机上运行
  • 如果需要 framework 既可以在模拟器上运行又可以在真机上运行,就需要使用 lipo -create 模拟器库 真机库 -output 最终库 进行合并
lipo -create 模拟器库内的exec文件路径 真机库内的exec文件路径 -output 合并后的库名
注意:此时只是把两个库的exec文件合并了, 然后把这个合并后的 exec 文件,替换两个库的任意一个,被替换的额那个库就是我们需要的了,此时这个库即可在模拟器上运行也可在真机上运行,亲测可行!

到此就结束了,怕自己忘了,记录一下...

iOS 点滴
Web note ad 1