常用做法
在IOS开发中,我们的基类往往会写一些空方法,然后让子类去实现,基类控制主要流程(这其实就是模板方法模式),这时我们往往这样写:
1 | - (void)mustBeOverriddenMethod { |
这样该方法如果直接被父类调用就会报异常,并且提示一定要被子类所覆盖。但是该方法存在如下弊端:
- 该方法一定要被调用才可以报异常,如果子类没有调用该方法,也没有覆盖该方法,父类在某些特定的情况下才调用该方法,那么就会出错。
- 不可以在该方法内部做一个基本的实现,然后被子类继承并且调用
[super mustBeOverriddenMethod]
- 如果项目中存在一个子类,但是暂时没有用到,并且其没有覆写这个方法,那么没有提示。以后其他人用这个类,很可能就会出错。
优雅的做法及疑问
以上这些问题都可以通过MustOverride框架来实现。
先来看下其用法,然后我们逐步分析其实现方式。
只要在父类需要被实现的方法内容添加一个宏:SUBCLASS_MUST_OVERRIDE
即可:
1 | - (void)someMethod { |
这样就可以了,并且更加神奇的是:
- 没有类调用该方法也可以报异常。
- 就算子类没有被用到也会报异常。
- 父类中可以做简单的实现,子类可以调用
super
来扩展该实现。
这时你可能产生如下疑问:
- 这个类没有用到为啥可以报异常?
- 它是怎样找到这个类的被标记了
SUBCLASS_MUST_OVERRIDE
的方法的?
对问题的剖析
一切都要从这个宏说起,进入宏的定义可以发现:
1 |
|
是不是感觉有些长?我们可以将该宏拆分:
1 |
|
首先定义了一个静态常量指针__must_override_entry__
,这个指针指向__func__
,也就是该宏所在方法的方法名。然后利用__attribute__
(编译器指令,可以在声明时做一些错误检查,或者一些优化),将其放入指定的section
中(关于section的定义会在后续章节中加以说明),我们可以在loader.h
中看到section是这样一个结构体:
1 | struct section { /* for 32-bit architectures */ |
从上面可以看出,used的意思是告诉编译器该静态变量要在该对象文件中被保留(尽管该变量是没有被引用的)。被标注的静态变量将会按照声明的顺序,放到指定的一个section中。使用__attribute__((section("name")))
可以指明该section.
那么放到section中的静态变量是怎样被使用的呢?
我们可以看到在load方法中,其调用了CheckOverrides
函数,也就是在该类加载到Runtime中的时候就被调用,不论其是否被使用。
1 | Dl_info info; |
从中可以看到其从Dl_info
中获取了section,
什么是Dl_info
,dladdr
?我们要从Linux指令集中去查找,
从其中的解释可以看出来,dladdr
可以用来确定addr
指明的地址是否存在于公用的对象中,这些对象是被调用程序所加载的。如果存在那么dladdr
会返回公用对象及重叠addr
的表示。该信息被封装到了Dl_info
结构体中。取出Dl_info
结构体中的dli_fbase
,然后调用getsectbynamefromheader_64
,就可以获取之前存储数据的section
。然后遍历该section
以找到所有被标识的方法。接下来利用RunTime
找到所有的子类:
1 | static NSArray *SubclassesOfClass(Class baseClass) |
判断某个类是否覆盖了方法:
1 | static BOOL ClassOverridesMethod(Class cls, SEL selector) |
如果没有覆盖则报异常。
小结:MustOverrid在编译期利用__attribute__((used,section("__DATA, MustOverride")))
来将方法名放到section
中,然后在文件加载到runtime的时候找到这个section
,进而找到对应地方法,找到所有的子类,利用runtime
判断其是否覆盖了父类的方法。
附:
关于load方法的几点说明:
在类或者分类被加载到Runtime的时候,会触发load
方法;并且只会在第一次被加载的时候被调用,所以只会调用一次。
load方法的调用顺序:
- 父类先调用
+load
方法,然后子类再调用。 - 分类调用
+load
方法要晚于原类。
延伸阅读
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0474e/BABHIIEF.html
http://tech.meituan.com/DiveIntoCategory.html
http://man7.org/linux/man-pages/man3/dladdr.3.html
https://www.bignerdranch.com/blog/inside-the-bracket-part-5-runtime-api/
http://nshipster.com/__attribute__/