浅析为何能通过FDStackView在iOS9以下使用UIStackView

前几天看到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

你可能感兴趣的:(浅析为何能通过FDStackView在iOS9以下使用UIStackView)