北戴河最孤独的图书馆
引言
我在github上写了一个 如果创建的时候PreferredStyle传入UIAlertControllerStyleActionSheet,则显示actionSheet。 大家可能很好奇,UIAlertController明明是iOS8才引入的,怎么能够在iOS8以下的系统中跑呢?下面进入正题。 简单来说就是三个字——黑魔法。 利用这种黑魔法的例子已经越来越多,我所知道的最早使用这种方法的是一个老外在三年为了解决NSUUID而使用的。 我们国内团队开发的 数据段的内存有些特殊,并不是我们理解的32上的指针是4Byte=32bit,64位上指针是8Byte=64bit,大家这里对数据段先有个概念,一会要用它来解释一些现象。 下面开始讲这个黑魔法能够实现的前提,是很重要的部分。在编译的时候,系统中的每个类都在数据段上有一个标签(形式是这样的:OBJC_CLASS$_ClassName),这个标签你可以理解成key,它的value就是该类的类名,举例:数据段中会有一个key是OBJC_CLASS$_UIAlertController,它对应的value就是UIAlertController的类名,当然也就会有OBJC_CLASS$_UIStackView这个标签,标识着UIStackView这个类。 最重要的一点是:在iOS7中,还没有UIAlertController的时候,这个标签OBJC_CLASS$_UIAlertController已经存在了,只是这个标签对应的value值是nil,因为没有这个类,我们可以认为是苹果在给高版本的这个类站位,就是苹果的这个站位才使得我们有幸用上了这个黑魔法。当然每个后出现的类都是有站位的,比如UIStackView。 if this label is Nil or doesnt exist, the class does not exist and cannot be allocated/used 这是我看到的老外在用该种黑魔法实现UUID的时候其中的一句说明,意思是:如果我们没找找到这个标签,就不能为该申请内存,也就不能使用了。 我对这句话的结论持怀疑态度,但又无法做实验验证,因为“标签站位”在早期版本中就存在了,而要找到“更早期”的版本验证该没有标签是很困难的,因为Xcode已经不能支持对“更早期”的版本的编译了,这段话表述有些混乱,大家还是往后看吧。 下面我们看看runtime里动态添加类的方法: Creates a new class and metaclass. @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class. @param name The string to use as the new class's name. The string will be copied. @param extraBytes The number of bytes to allocate for indexed ivars at the end of @return The new class, or Nil if the class could not be created (for example, the desired name is already in use). 大家看官方对函数的说明可以知道:superClass 是你要添加的类的父类,name是你要添加的类的名字,extraBytes一般传0,它会返回一个新类,如果名字被占用了会返回Nil。 1.如果OBJC_CLASS$_ClassName标签存在,但是对应的类不存在(相当于有key,但是value是nil)此时动态添加类是可以成功的。 2.如果OBJC_CLASS$_ClassName标签和对应的类都有的话,此时动态添加类是不成功的,返回nil。 我们黑魔法的实现思路就是基于这两个重要结论,下面我们具体看代码。 这是一段汇编代码,不用担心看不懂它,我也不懂汇编,这不影响我们分析,我简单的解释一下: 1.__asm是在C、C++源码中放入汇编代码(OC是C的超集)。 2..align是对指令或数据的存放地址进行对齐,有些CPU架构要求固定的指令长度,并且存放地址相对于2的幂指数圆整,否则无法运行,比如arm。有些不要这样也能运行,就是执行效率稍微低点,如i386。 3.64位的对齐方式是8位(23(.align后面的数)),32位的对齐方式是4位(22(.align后面的数))。对齐只对紧挨着它的那条语句起作用,既,L_OBJC_CLASS_UIAlertController或_OBJC_CLASS_UIAlertController。 4..quad声明一组数占64位,.long声明一组数占32位 5..secton 后是指定参数用的,上述汇编的大体意思是在数据段(就是我们之前提到的数据段)找到OBJC_CLASS$_UIAlertController标签并利用.quad、.long声明的一组数来存放它,取名为:_OBJC_CLASS_UIAlertController。 这是一段枯燥又非重点的代码,如果大家心情不好直接忽略掉就可以了。 大家坚持住,这是要分析的最后一段代码了。原理概述
the class and metaclass objects. This should usually be \c 0.OBJC_EXPORT Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
由此要说明的两个重要结论:
代码讲解
__asm( ".section __DATA,__objc_classrefs,regular,no_dead_strip\n"#if TARGET_RT_64_BIT
".align 3\n"
"L_OBJC_CLASS_UIAlertController:\n"
".quad _OBJC_CLASS_$_UIAlertController\n"#else
".align 2\n"
"_OBJC_CLASS_UIAlertController:\n"
".long _OBJC_CLASS_$_UIAlertController\n"#endif
".weak_reference _OBJC_CLASS_$_UIAlertController\n");
__attribute__((constructor)) static void GJAlertControllerPatchEntry(void) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @autoreleasepool { // >= iOS8.
if (objc_getClass("UIAlertController")) { return;
}
Class *alertController = NULL;#if TARGET_CPU_ARM
__asm("movw %0, :lower16:(_OBJC_CLASS_UIAlertController-(LPC0+4))\n"
"movt %0, :upper16:(_OBJC_CLASS_UIAlertController-(LPC0+4))\n"
"LPC0: add %0, pc" : "=r"(alertController));
#elif TARGET_CPU_ARM64
__asm("adrp %0, L_OBJC_CLASS_UIAlertController@PAGE\n"
"add %0, %0, L_OBJC_CLASS_UIAlertController@PAGEOFF" : "=r"(alertController));
#elif TARGET_CPU_X86_64
__asm("leaq L_OBJC_CLASS_UIAlertController(%%rip), %0" : "=r"(alertController));
#elif TARGET_CPU_X86
void *pc = NULL;
__asm("calll L0\n"
"L0: popl %0\n"
"leal _OBJC_CLASS_UIAlertController-L0(%0), %1" : "=r"(pc), "=r"(alertController));
#else#error Unsupported CPU#endif
if (alertController && !*alertController) {
Class class = objc_allocateClassPair([GJAlertController class], "UIAlertController", 0); if (class) {
objc_registerClassPair(class);
*alertController = class;
}
}
}
});
}
__attribute__((constructor)) static void GJAlertControllerPatchEntry(void){
}
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章