静态库和动态库的制作以及Bundle资源文件的使用

96
Mark_Guan
0.1 2016.12.09 17:41* 字数 2945

前两天因为公司业务上的需要,我们需要和联通公司那边进行业务集成,考虑到一些业务上的隐私性,我们将提供的内容打包成了一个静态库,只为对方提供了一些接口,今天正好有空整理一下,填一下这个过程中遇到的一些坑,以方便日后查阅。

什么是库?

库是程序代码的集合,是共享程序代码的一种方式。我们根据源代码的公开情况,可以将库分为 2 种类型

  1. 开源库:公开源代码,我们能看到代码的具体实现,比如 SDWebImageAFNetworking等;如何将自己的代码发布到代码托管平台中,然后借助CocoaPods 供别人使用,我们在上一篇文章中已经讲过,这里不再细说。
  2. 闭源库:不公开源代码,是经过编译后的二进制文件,看不到代码的具体实现。闭源库主要分为:静态库、动态库

静态库和动态库的存在形式以及使用上的区别

存在形式:
静态库:.a 和 .framework
动态库:.dylib 和 .framework

区别:
静态库:链接时,静态库会被完整地复制到可执行文件中, 被多次使用就有多份冗余拷贝 ,如下图:

静态库

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,以节省内存,具体形式如下图所示:

动态库

静态库的使用场景:

  1. 项目开发中我们经常使用到的极光推送、百度地图、友盟分享等等。这些大公司都有自己的核心业务,同时又希望我们去使用他们提供的技术,但是又不想暴露他们的代码,因此他们采用"闭源"的方式来让我们集成。
  2. 将MRC的项目放到ARC环境下,我们可以如下图去操作


    MRC->ARC

    但是如果文件特别多,我们这样一个个去操作是不是很麻烦?其实我们可以将MRC的项目,打包成静态库, 可以在ARC下直接使用, 不需要转换,非常方便;

知识准备:
我们都知道模拟器下的静态库和真机下的静态库是不能混用的,那为什么呢?其实主要原因是模拟器和真机的CPU架构不一样(各个模拟器型号之间架构也不一样)不同机型的CPU, 对应的架构不同;
模拟器:

  1. 4s和5使用的是i386
  2. 5s-6sPlus使用的是x86_64

真机:

  1. 3gs---4s : armv7
  2. 5/5c : armv7s(armv7兼容armv7s)
  3. 5s---6sPlus:arm64

我们可以分别选中不同的模拟器, 进行编译,然后利用lipo -info xxx.a 查看静态库所支持的架构, 如下图所示:

查看静态库支持架构

因为CPU架构的不同,这就造成了一个问题,如果我是在6s环境下编译生成的静态库运行到5上面就会编译不通过, 怎样可以一次编译支持多个架构的的静态库呢?
可以通过Build Settings -> Build Active -> NO,表示不止编译活跃的架构, 让所有的架构都编译,如下图所示:

编译所有架构

.a静态库的制作步骤

  1. 新建项目-> 选择 “Cocoa Touch Static Library”


    新建静态库
  2. 添加库需要包含的源代码,并且设置需要暴露的头文件:


    3.png

    引入制作的静态库所需要的网络框架(有些会自动引入,有些需要手动引入,根据编译报错,可以检查。)


    引入框架
  3. 设置支持所有架构:


    设置支持架构
  4. 编译前检查一下是debug模式还是release模式,选择release模式。在模拟器和真机中分别编译后,libzpstaticLibrary.a就由红色变成了黑色,Show in Finder在目录中查看:


    Release模式

.framework静态库的制作步骤

  1. 新建项目-> 选择 “Cocoa Touch Framework”


    Framework
  2. 添加库需要包含的源代码,并且设置需要暴露的头文件:


    暴露头文件

    如果用户需要导入的头文件过多怎么办?我们可以使用一个主头文件包含其他头文件, 让用户只导入一个主头文件。

  3. 编译时, 设置编译所有架构


    设置编译架构
  4. 因为默认制作的是动态库,我们 需要设置链接类型target -> Build Settings-> 搜索 Mach-o Type ;改为静态库

    设置类型

    如果没有这一步,我们在测试的时候会报一个错误:
    错误提示

    这个错误是因为我们默认生成的是一个动态库,如果我们把它当成一个动态库来用的话 就需要在测试工程中General->Embedded Binaries中导入我们这个动态库,这样就不会报这个错误了。
    动态库

经过以上步骤后我们就可以成功的生成了一个.Framework的静态库或者是动态库了。

静态库的操作

合并静态库

因为静态库针对于模拟器和真机生成了不同版本(支持不同架构), 所以没法同时运行,但是我们可以将二者进行合并,合并后的.a大小大约是不合并的2倍左右。
cd 到一个目录,然后通过

lipo -create Debug-iphoneos/libTools.a   Debug-iphonesimulator/libTools.a  -output  libTools.a

进行合并,合并后的架构如下图所示:

分解合并库

既然有合并静态库,那么对应的也有分解合并库,例如我们在发布的时候只想要使用arm64架构,那么我们可以通过下面这行命令来达到我们目的:

lipo -thin arm64 静态库 -output 新的静态库名称

如下图所示:


查看分解之后的库可以发现该库已经非常小了

从合成库中移除某个架构

如果合成库中的某一个架构我们用不到了,想要移除,那么我们可以通过下面的命令,例如我们想要将合成库中的i386移除

lipo -remove i386 静态库 -output 静态库名称

执行命令后我们可以发现,合成的静态库中已经没有了i386这个架构了

总结

.a静态库和.framework静态库的区别?

我们都知道静态库包括.a.framework,那么二者之间到底有什么区别呢?

  1. .a是一个纯二进制文件, 而.framework中除了有二进制文件之外还有资源文件,比如Bunle、Plist等。
  2. .a文件不能直接使用, 至少要有.h文件的配合; .framework文件可以直接使用
  3. .a + .h + sourceFile = .framework
  4. 建议使用.framework

静态库的调试

因为静态库只提供了一些头文件,实现代码都是经过二进制化的,如果我们想对静态库进行断点调试如何做呢?
我们可以创建复合项目来对静态库进行调试:

  1. 创建一个测试工程,选中TARGETS,点击下面的➕按钮,添加一个静态库,如下图所示
    复合项目
  2. 设置主工程文件和静态库的依赖关系。


    添加依赖关系

经过以上两步后我们就可以对要制作的静态库文件进行调试开发了,是不是很爽,至于制作静态库就和上面的步骤一样了。

Bundle封装XIB、图片等资源文件

创建Bundle文件

因为我是新建了一个复合测试工程,所以在这里为了方便我们直接在复合测试工程中添加了一个子工程,因为iOS框架中没有bundle,要选中macOS框架找到bundle,如下图为我们的Bundle命名为STWNetPay


image

Bundle的本质其实是一个目录,我们完全可以创建一个文件夹,然后把需要封装的资源文件拷贝到该目录下,然后将后缀名为改成bundle即可,但是如果我们想要将XIB文件封装到Bundle文件中的话,这种方法就不可以了. 考虑到xib是文本文件,编译后要被序列化为二进制的nib文件,使用时将nib文件反序列化,就可以正常显示界面了。而bundle本身是静态的,其内部的资源包不参与项目的编译,所以,此处必须创建工程把xib序列化为二进制的nib,否则的话,直接创建文件夹后改名就可以了。

设置Bundle

1.创建好之后我们发现里面只有一个Info.plist文件,经测试该文件不能删掉,如果删掉之后编译Bundle文件会报错:bundle format unrecognized, invalid, or unsuitable

2.iOS Deployment Target改为你支持的最低版本


image

3.设置base SDK 为latest iOS


image

4.COMBINE_HIDPI_IMAGES设置为NO
因为iOS创建Bundle时放入的图片资源(.png)在默认配置下会被转为.tiff格式,使用的时候找不到。所以找到bundle的工程,Buld Settings > COMBINE_HIDPI_IMAGES设置为NO
image

5.Skip Install 设置为NO


image

6."Build Active Architecture Only" 设置为 NO
向Bundle中添加资源文件
image

一切就绪,按快捷键"Command + B"编译,编译成功后,我们的Bundle文件即由红色变成了黑色,表示编译成功,这个时候找到我们的Bundle文件,显示报内容,如下图所示:


image

可以看到XIB文件经过编译已经变成了nib文件,这就是我们想要的.

使用bundle里的资源文件

将编译好之后的Bundle文件拖入我们的静态库中,因为是要在静态库中使用Bundle文件,而不是在MainBundle中,所以我们需要先找到我们Bundle所在的路径才能使用它里面的资源.

创建一个管理Bundle路径的类BundleTools,专门用来获取我们Bundle所在的路径:

#import <Foundation/Foundation.h>
#define BUNDLE_NAME @"STWNetPay"
@interface BundleTools : NSObject

+ (NSString *)getBundlePath: (NSString *) assetName;
+ (NSBundle *)getBundle;

@end

#import "BundleTools.h"

@implementation BundleTools

+ (NSBundle *)getBundle{
    NSURL    *url=  [[NSBundle bundleForClass:[self class]] URLForResource:BUNDLE_NAME withExtension:@"bundle"];
    NSBundle *bundle = [NSBundle bundleWithURL:url];
    return bundle;
}

+ (NSString *)getBundlePath: (NSString *) assetName{    
    NSBundle *myBundle = [BundleTools getBundle];
    if (myBundle && assetName) {
        return [[myBundle resourcePath] stringByAppendingPathComponent: assetName];
    }
    return nil;
}
@end
使用图片
UIImage *image = [UIImage imageWithContentsOfFile: [BundleTools getBundlePath: @"nav_back"]];
使用XIB

控制器是XIB的情况,此时应该在控制器中重写 Init 方法

- (instancetype)init{
    NSBundle *myBundle = [BundleTools getBundle];
    //从bundle中获取界面文件
    self = [super initWithNibName: [NSString stringWithUTF8String: object_getClassName(self)] bundle: myBundle];
    if (self) {
        // Custom initialization
    }
    return self;
}

否则

 NSBundle *myBundle = [BundleTools getBundle];
    //从bundle中获取界面文件
 view = [myBundle loadNibNamed:@"" owner:nil options:nil].firstObject;

Swift打包动态库

  1. 因为Swift项目是不支持静态库的,必须使用动态库。所以我们在新建一个静态库的时候,一定要选择Cocoa Touch Framework
    创建动态库

    如果我们在项目中将target -> Build Settings-> 搜索 Mach-o Type ; 改为Static Library
    静态库

    Command+B编译,系统会报一个错,提示我们Swift is not supported for static libraries.如下图:
    静态库报错

    所以,如果在Swift项目中,我们必须使用动态库;
  2. 因为Swift是没有头文件这个说法的,所以在Build Phases中我们也不用设置暴露头文件了
    11.png
  3. 确定支持模拟器或者真机中的所有架构


    编译架构
  4. 最重要的,因为swift没有头文件,所以如果我们想让别人用到我们的方法,就用Public修饰符修饰。
    暴露方法

    这样生成的动态库中就会将我们的Swift方法转换成了OC的方法
SWIFT_CLASS("_TtC10zpSwiftLib4Tool")
@interface Tool : NSObject
+ (void)Log;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

这样将生成的动态库拖入到我们的测试工程中就可以成功的使用了,但是和静态库有一点不同的是,我们需要在测试工程中General->Embedded Binaries中导入我们这个动态库即可。

谢谢大家,欢迎一起讨论学习。

OC笔记
Web note ad 1