国庆长假之iOS动态库

刚接触到动态库的时候,那时候带着一个问题,今天有空就把它解了。祝大家国庆长假愉快~~~

动态库和可执行文件,这两种类型的Mach-O产物,如果里面用到了相同的第三方库会怎么样,而这些第三方库可能版本也是不一样的。版本不一样带来功能上的差异,最糟糕的情况,软件会无端端crash。但是事实上,在动态库的使用上,是不会出现上面说的情况的。这主要归功于:two-level namespace

Two-level namespace

google 一下,发现苹果是使用了two-level namespace解决上面的问题的。关于two-level namespace的解析,详情请看这里

With the two-level namespace feature enabled, when the static linker records references to imported symbols, it records a reference to the name of the library that contains the symbol and the name of the symbol. Linking your programs with the two level namespace feature offers two benefits over the flat namespace:

Enhanced performance when searching for symbols. With the two-level namespace, the dynamic linker knows exactly where to start looking for the implementation of a symbol. With a flat namespace, the dynamic linker must search all the loaded libraries for the one that contains the symbol.

Enhanced forward compatibility. In the flat namespace, two or more libraries cannot contain symbols with different implementations that share the same name because the dynamic linker cannot know which library contains the preferred implementation. This is not initially a problem, because the static linker catches any such problems when you first build the application. However, if the vendor of one of your dependent shared libraries later releases a new version of the library that contains a symbol with the same name as one in your program or in another dependent shared library, your program will fail to run.

简单来说,符号 = 库的名称 + 原来的符号名称。这样static linker 在链接的时候就可以知道符号来自哪里了,不但优化了搜索符号的速度,也为后面的扩展性进行了兼容。Talk is cheap....

Demo time

通过demo来加深理解吧,以后看到这些符号的时候会有更深一层的理解。

Framework1 和Framework2 都为动态库,Logger 类同时存在于这两个Framework中,编译运行的结果,它们都正确的输出各自对应的logger。

//  Logger.m
//  Framework1
@implementation Logger
- (void)log
{
    NSLog(@"Framework1 logger");
}
@end


//  Logger.m
//  Framework2
@implementation Logger
- (void)log
{
    NSLog(@"Framework2 logger");
}
@end

输出结果如下:
=======================================================
2017-10-02 10:21:13.741 WorkWithFrameworks[6867:990637] Framework1 logger
2017-10-02 10:21:13.741 WorkWithFrameworks[6867:990637] Framework2 logger

一样的符号,不一样的实现。two-level namespace 使得这两个动态库可以正常工作。使用hopper查看一下,符号如下:

cfstring_Framework1_logger

cfstring_Framework2_logger

Problem solved.问题是解决了,发现有几个知识点需要再次学习一下。two-level namespace 算是overture 吧。带着下面几个问题,继续深挖。。。

  • 可执行文件与动态库同为mach-o 的最终产物,它们的关系是什么
  • 动态库与可执行文件作为最终产物,它为什么比可执行文件大
  • 静态库也支持two-level namespace吗?
  • two-level namespace 解决了符号重复的问题,但是类名也还是那个类名,会不会有问题

编译的一些细节问题

  • 每个源代码由编译器编译成一个.o 文件,.o文件是中间产物
  • 静态库由多个 .o文件组成
  • 由单个或者多个文件由linker链接成可执行文件,其中没有用到的.o文件不会参与链接
  • 动态库是最终产物,不参与链接。在可执行文件中有一条对这个动态库的引用,可执行文件在加载的时候会先加载动态库
  • 动态库与可执行文件都是mach-o文件

可执行文件与动态库同为mach-o 的最终产物,它们的关系是什么?

可执行文件与动态库同为mach-o文件,当可执行文件里面用到动态库,就会在其内部保留对动态库的引用。

动态库与可执行文件作为最终产物,它为什么比可执行文件大

动态库里面所有的.o都会被保留,因为它无法确定哪些符号会被外部引用,因此一般的动态库比可执行文件要大

静态库也支持two-level namespace吗?

只有mach-o的文件才支持two-level namespace功能。

通过demo可以知道,静态库libStaticLibrary1.a 与libStaticLibrary2.a,里面都是.o文件的集合,生成可执行文件的时候,只选取其中一份logger代码,至于选取那一份,跟编译顺序有关。这样会存在另外一个问题:这两个静态库依赖的类虽然是一样的,但是可能实现不一样,这样就会出现意想不到的问题了,最严重就是crash了。

FYI ,之前已经吃过一次这样的亏了,详情请查看:Release 崩溃 ,Debug 不崩? ,自己制作静态库的时候,一定要考虑好兼容性问题,对于一些外部饮用的第三方库除了要特别注意外,还可以通过加前缀来区分。


从上图可以看出,首先编译的是StaticLibrary1。

查看可执行文件可以知道,最终链接进入的是StaticLibrary1 版本的logger。

two-level namespace 解决了符号重复的问题,但是类名也还是那个类名,会不会有问题?

KVO 的实现简单来说就是创建了一个NSKVONotifying_A的新类,继承被观察的类A,它替换原有A类的isa 指针,重写了所提供的keyPath 相关的setter方法,实现观察A类属性变化。平时用,一般不会有什么问题,但是当遇上动态库,情况就不一样了。

在KVO发生的时候,中间类是动态生成添加的新类。查看runtime的源码,了解一下类添加的过程。

添加一个新类,首先调用objc_allocateClassPair ,然后是objc_registerClassPair。下面来看看objc_allocateClassPair

/***********************************************************************
* objc_allocateClassPair
* fixme
* Locking: acquires runtimeLock
**********************************************************************/
Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;
    rwlock_writer_t lock(runtimeLock);

    // Fail if the class name is in use.
    // Fail if the superclass isn't kosher.
    if (getClass(name)  ||  !verifySuperclass(superclass, true/*rootOK*/)) {
        return nil;
    }
    // Allocate new classes.
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);
    objc_initializeClassPair_internal(superclass, name, cls, meta);
    return cls;
}

static Class getClass(const char *name)
{
    runtimeLock.assertLocked();

    // Try name as-is
    Class result = getClass_impl(name);
    if (result) return result;

    // Try Swift-mangled equivalent of the given name.
    if (char *swName = copySwiftV1MangledName(name)) {
        result = getClass_impl(swName);
        free(swName);
        return result;
    }
    return nil;
}

注意到getClass,当getClass返回不为空的时候,代表该class已经存在了。也就是说,当两个动态库里面或者动态库与可执行文件里面包含一样的类,虽然two-level namespace可以在符号级别区分它们,但是如果它们各自实现了该类的KVO。那么,因为类名是一样的,所以NSKVONotifying_xxx 也是一样的。后来者注册KVO的时候,由于该类已经存在了,会导致objc_allocateClassPair返回nil。

后面者注册KVO的时候会失败或者导致程序崩溃。

写完收工,祝大家国庆快乐~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容