浅析为何能通过FDStackView在iOS9以下使用UISta
前几天看到sunnyxx团队的新作FDStackView。大家都知道在iOS9苹果提供了一个新的玩具UIStackView,然而在iOS9以前是没有办法使用的。可通过FDStackView你却可以在iOS9以前的系统上使用UIStackView,更重要的是我们不需要去做任何额外的工作,FDStackView会自动为我们处理好一切。
今天分析了一下是如何实现这个神奇的功能的,在FDStackView.m
中嵌入了一段内联汇编:
__asm(
".section __DATA,__objc_classrefs,regular,no_dead_strip\n"
#if TARGET_RT_64_BIT
".align 3\n"
"L_OBJC_CLASS_UIStackView:\n"
".quad _OBJC_CLASS_$_UIStackView\n"
#else
".align 2\n"
"_OBJC_CLASS_UIStackView:\n"
".long _OBJC_CLASS_$_UIStackView\n"
#endif
".weak_reference _OBJC_CLASS_$_UIStackView\n"
);
这段代码的主要作用是在DATA这个segment中暴露了L_OBJC_CLASS_UIStackView这个符号,它指向了符号OBJC_CLASS$_UIStackView(编译器硬编码确定)。
然后在类被成功加载之后会调用FDStackViewPatchEntry
,这里面首先判断如果是iOS9的话那就啥也不需要做了。
这里面比较关键的代码在这里:
#if TARGET_CPU_ARM
__asm("movw %0, :lower16:(_OBJC_CLASS_UIStackView-(LPC0+4))\n"
"movt %0, :upper16:(_OBJC_CLASS_UIStackView-(LPC0+4))\n"
"LPC0: add %0, pc" : "=r"(stackViewClassLocation));
#elif TARGET_CPU_ARM64
__asm("adrp %0, L_OBJC_CLASS_UIStackView@PAGE\n"
"add %0, %0, L_OBJC_CLASS_UIStackView@PAGEOFF" : "=r"(stackViewClassLocation));
#elif TARGET_CPU_X86_64
__asm("leaq L_OBJC_CLASS_UIStackView(%%rip), %0" : "=r"(stackViewClassLocation));
#elif TARGET_CPU_X86
void *pc = NULL;
__asm("calll L0\n"
"L0: popl %0\n"
"leal _OBJC_CLASS_UIStackView-L0(%0), %1" : "=r"(pc), "=r"(stackViewClassLocation));
#else
#error Unsupported CPU
#endif
这里判断了CPU的指令集,以ARM为例说明。
由于不能将一个32位的常量直接存入一个寄存器中,所以需要分别取它的高16位和低16位存入。
完了之后将pc和%0相加存入%0中,然后将输入保存到stackViewClassLocation。
接着通过runtime的objc_allocateClassPair创建了一个名为UIStackView的类,并在stackViewClassLocation指针所指向的空间中写入新创建的class,然后FDStackView就华丽变身为UIStackView了。
结尾:
由于我本人对汇编也不是很熟悉,只是简单的分析了一下,所以如果有不对的地方麻烦大家不吝赐教共同学习:)
BTW,这样的方式如果是为UIStackView写了category也没办法使用了,因为分类是写在原来的类上面的,暂时还不知道怎么解决。
参考链接:
http://www.ethernut.de/en/documents/arm-inline-asm.html
http://hailoong.sinaapp.com/?p=125