iOS 开发中的『库』

看文章之前,你可以看下下面几个问题,如果你都会了,或许可以不看。

  • .framework 是什么?怎么制作?
  • 谈一谈自己对动态库和静态库的理解。
  • 在项目中如何使用动态framework的 APP ?使用了动态framework 的 APP 能上架 Appstore 么?
  • 可以通过 framework 的方式实现 app 的热修复么?

动态库 VS. 静态库

首先你得搞清楚,这两个东西都是编译好的二进制文件。就是用法不同而已。**为什么要分为动态和静态两种库呢?**先看下图:

请添加图片描述
请添加图片描述

app 可执行文件

这个目标 app 可执行文件就是 ipa解压缩后,再显示的包内容里面与app同名的文件

静态库

对于静态库而言,在编译链接的时候,会将静态库的所有文件都添加到 目标 app
可执行文件
中,并在程序运行之后,静态库与 app 可执行文件 一起被加载到同一块代码区中

苹果的官方文档中有静态库的解释

  • 静态库:当程序在启动的时候,会将 app 的代码(包括静态库的代码)一起在加载到 app 所处的内存地址上。
  • 相比于静态库 的方案,使用动态库将花费更多的启动时间和内存消耗。还会增加可执行文件的大小。

动态库

编译链接的时候,只会将动态库被引用的头文件添加到目标 app 可执行文件

苹果的官方文档中有对动态库的解释

  • 动态库:可以在 运行 or 启动 的时候加载到内存中,加载到一块**独立的于 app ** 的内存地址中

举个例子

假设 UIKit 编译成静态库和动态库的大小都看成 1M , 加载到内存中花销 1s现在又 app1 和 app2 两个 app

  • app1 静态库和app2 静态库

app1 启动的时候, 需要花销 2s
同时内存有 2M 分配给了 app1

同样的道理 加上 app2 的启动时间和内存消耗

采用静态库的方案,一共需要花销 4s 启动时间4M 内存大小4M 安装包大小

  • app1 动态库和app2 动态库

对于启动和 app1 可能花费一样的时间

但是在启动 app2 的时候 不用再加载 UIKit 动态库 了。减少了 UIKit 的重复 使用问题,

一共花销 3s启动时间、3M 内存大小、4M 安装包大小。

动态库 和静态库的总结

动态库的形式 来优化系统

而很多 app 都会使用很多相同的库,如 **UIKit **、 CFNetwork 等

苹果为了加快 app 启动速度、
减少内存消耗、
减少安装包体积大小,
采用了大量 动态库的形式 来优化系统

dyld 的共享缓存

在 OS X 和 iOS 上的动态链接器使用了共享缓存,共享缓存存于 /var/db/dyld/。对于每一种架构,操作系统都有一个单独的文件,文件中包含了绝大多数的动态库,这些库都已经链接为一个文件,并且已经处理好了它们之间的符号关系

  • 当加载一个 Mach-O 文件 (一个可执行文件或者一个库) 时,动态链接器首先会检查 共享缓存
    看看是否存在其中,如果存在,那么就直接从共享缓存中拿出来使用
  • 每一个进程都把这个共享缓存映射到了自己的地址空间中。这个方法大大优化了 OS X 和 iOS 上程序的启动时间。

两者都是由*.o目标文件链接而成。都是二进制文件,闭源。

.framework VS .a

  • .a是一个纯二进制文件,不能直接拿来使用,需要配合头文件、资源文件一起使用。在 iOS 中是作为静态库的文件名后缀。
  • .framework中除了有二进制文件之外还有资源文件,可以拿来直接使用。
  • 在不能开发动态库的时候,其实 『.framework = .a + .h + bundle』。而当 Xcode 6
    出来以后,我们可以开发动态库后**『.framework = 静态库/动态库 + .h + bundle』**

.tbd VS .dylib

  • 对于静态库的后缀名是**.a**,那么动态库的后缀名是什么呢?
  • 可以从 libsqlite3.dylib 这里我们可以知道 .dylib 就是动态库的文件的后缀名。
  • 那么 .tbd 又是什么东西呢?其实,细心的朋友都早已发现了从 Xcode7 我们再导入系统提供的动态库的时候,不再有**.dylib了,取而代之的是.tbd**。而 .tbd 其实是一个YAML本文文件,描述了需要链接的动态库的信息。主要目的是为了减少app 的下载大小。具体细节可以看这里

小总结

  • 首先,相比较与静态库和动态库,动态库在包体积、启动时间还有内存占比上都是很有优势的。
  • 为了解决 .a 的文件不能直接用,还要配备 .h 和资源文件,苹果推出了一个叫做 .framework 的东西,而且还支持动态库。

Embedded VS. Linked

OK,前面说了那么多,那么如果我们自己开发了一个动态framework 怎么把它复制到 dyld 的共享缓存 里面呢?

  • 一般来说,用正常的方式是不能滴,苹果也不允许你这么做。(当然不排除一些搞逆向的大神通过一些 hack 手段达到目的)
  • 那么,我们应该如何开发并使用我们自己开发的 动态framework 呢?
  • 那就是 Embedded Binaries。

Embedded Binaries

Embedded 的意思是嵌入,但是这个嵌入并不是嵌入 app 可执行文件,而是嵌入 app 的 bundle 文件。当一个 app 通过 Embedded 的方式嵌入一个 app 后,在打包之后解压 ipa 可以在包内看到一个 framework 的文件夹,下面都是与这个应用相关的动态framework。在 Xcode 可以在这里设置,图中红色部分:

请添加图片描述

那么问题又来了,下面的 linked feameworks and libraries 又是什么呢?

首先在 linded feameworks and libraries
这个下面我们可以连接系统的动态库、自己开发的静态库、自己开发的动态库。

  • 对于这里的静态库而言,会在编译链接阶段连接到app可执行文件中,
  • 而对这里的动态库而言,虽然不会链接到app可执行文件中,但是会在启动的时候就去加载这里设置的所有动态库

如果你不想在启动的时候加载动态库,可以在 linded feameworks and libraries
删除,并使用dlopen加载动态库。(dlopen 不是私有 api。)

- (void)dlopenLoad{
    NSString *documentsPath = [NSString stringWithFormat:@"%@/Documents/Dylib.framework/Dylib",NSHomeDirectory()];
    [self dlopenLoadDylibWithPath:documentsPath];
}

- (void)dlopenLoadDylibWithPath:(NSString *)path
{
    libHandle = NULL;
    libHandle = dlopen([path cStringUsingEncoding:NSUTF8StringEncoding], RTLD_NOW);
    if (libHandle == NULL) {
        char *error = dlerror();
        NSLog(@"dlopen error: %s", error);
    } else {
        NSLog(@"dlopen load framework success.");
    }
}

关于制作过程

关于如何制作,大家可以看下raywenderlich家的经典教程《How to Create a Framework for iOS 》,中文可以看这里《创建你自己的Framework》

阅读完这篇教程,我补充几点。

  • 首先,framework 分为Thin and Fat Frameworks。Thin 的意思就是瘦,指的是单个架构。而 Fat
    是胖,指的是多个架构。
  • 要开发一个真机和模拟器都可以调试的 Frameworks 需要对Frameworks进行合并。合并命令是 lipolipo。 如果 app
    要上架 appstore 在提交审核之前需要把 Frameworks 中模拟器的架构给去除掉。
  • 个人理解,项目组件化或者做 SDK 的时候,最好以 framework 的形式来做。

framework 的方式实现 app 的热修复

实现大致思路

  • 下载新版的 framework
  • 先到 document 下寻找 framework。然后根据条件加载 bundle or document 里的 framework。
	
	NSString *fileName = @"remote";
	
	NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentDirectory = nil;
	if ([paths count] != 0) {
		documentDirectory = [paths objectAtIndex:0];
	}

	NSFileManager *manager = [NSFileManager defaultManager];
	NSString *bundlePath = [[NSBundle mainBundle]
										 pathForResource:fileName ofType:@"framework"];
	
	BOOL loadDocument = YES;
	
	// Check if new bundle exists
	if (![manager fileExistsAtPath:bundlePath] && loadDocument) {
		bundlePath = [documentDirectory stringByAppendingPathComponent:[fileName stringByAppendingString:@".framework"]];
	}


  • 再加载 framework
	// Load bundle
	NSError *error = nil;
	NSBundle *frameworkBundle = [NSBundle bundleWithPath:bundlePath];
	
	if (frameworkBundle && [frameworkBundle loadAndReturnError:&error]) {
		NSLog(@"Load framework successfully");
	}else {
		NSLog(@"Failed to load framework with err: %@",error);
		
	}

  • 加载类并做事情
	// Load class
	Class PublicAPIClass = NSClassFromString(@"PublicAPI");
	if (!PublicAPIClass) {
		NSLog(@"Unable to load class");
		
	}
	
	NSObject *publicAPIObject = [PublicAPIClass new];
	[publicAPIObject performSelector:@selector(mainViewController)];

番外篇

从源代码到app

当我们点击了 build 之后,做了什么事情呢?

  • 预处理(Pre-process):把宏替换,删除注释,展开头文件,产生 .i 文件。
  • 编译(Compliling):把之前的 .i 文件转换成汇编语言,产生 .s文件。
  • 汇编(Asembly):把汇编语言文件转换为机器码文件,产生 .o 文件。
  • 链接(Link):对.o文件中的对于其他的库的引用的地方进行引用,生成最后的可执行文件(同时也包括多个 .o 文件进行 link)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值