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

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

作者: Mark_Guan | 来源:发表于2016-12-09 17:41 被阅读1926次

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

    什么是库?

    库是程序代码的集合,是共享程序代码的一种方式。我们根据源代码的公开情况,可以将库分为 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中导入我们这个动态库即可。

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/dztsmttx.html