常见面试题:介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
深入了解Category
我们都知道OC代码执行时会先转成C\C++代码,OC对象转成对应的结构体;
Category对应的结构体我们可以通过将分类的.m文件转成c++文件查看:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc LZPerson+text.m
即可生成对应的.cpp文件;
在.cpp文件中搜索Category_t可以看到Category对应的结构体:
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
通过观察结构体我们可以发现分类对应的结构体中有存储实例方法的列表、存储类方法的列表、存储协议的和属性的列表。
到此可知面试题部分答案:
分类一般用来动态的为已经存在的类扩展新的方法。
分类中可以添加实例方法、类方法、属性、协议。但是不能添加成员变量。
添加属性的话只会生成set、get方法的声明不会实现也不会生成下划线成员变量。
如何‘覆盖’了原来的方法?
首先我们得知道:方法的调用就是消息的发送,如果调用实例方法,就是通过isa去类对象中查找对应的方法,如果调用类方法、就是通过isa去元类对象中查找对应的方法。
但是加入分类后通过isa去对应类或元类中查找时却能查到分类中写的方法!
所以我们可以得知:系统是在运行时把分类中的信息整合到了原来的类中!
到底推测是否正确以及如何整合,我们可以通过探究运行时的源码来分析。
源码下载地址:https://opensource.apple.com/tarballs/
搜索objc4下载最新源码。
这里不张贴源码代码,只是把一步步的入口写出来,有兴趣的可根据以下的步骤去研读源码。
打开源码后首先找到Runtime的入口文件objc-os.mm然后找到初始化方法-objc_init(void)
->map_images()//镜像加载
->map_images_nolock()
->_read_images
->下滑找到//Discover categories
->remethodizeClass//重置类的方法
->attachCategories//绑定分类
->attachLists
->最核心的函数memmove和memcopy
memmove:将原来类中的信息列表在内存中向后移动,移动的大小就是分类中的信息所占大小
memcopy:将分类中的信息复制到上一步移动出来的空间。
通过源码分析可知:系统是在运行时将分类中对应的实例方法、类方法等插入到了原来类或元类的方法列表中,且是在列表的前边!所以,方法调用时通过isa去对应的类或元类的列表中查找对应的方法时先查到的是分类中的方法!查到后就直接调用不在继续查找。这即是’覆盖’的本质!
存在多个分类,调用谁?
当有多个分类时,会调用哪个分类中的方法呢?
这个是与编译顺序有关,最后编译的分类中对应的信息会在整合在类或元类对应列表的最前边。所以是调用最后编译的分类中的方法!可以查看Build Phases ->Complie Source 中的编译顺序!