类结构分析下+经典面试题分析

在上一篇文章中我们已经探讨了isa的走向和类的结构分析,这一篇我们讲继续探索,然后分享两个经典的面试题。

上次文章结尾留下个问题,就是没有打印出成员变量,是怎么回事呢,其实它们也是存在了class_rw_t里面,下面我们把它们打印出来

类信息

先看一下类里面有哪些东西

然后把属性和实例方法打印出来,这在上篇博客中已经打印过


打印属性和实例方法

发现没有成员变量,那么到底是怎么回事呢, 其实还是上篇博客讲到的,一定是存在'class_rw_t'里面的,我们只要找出来即可,那么我们先把ro打印出来

打印ro
打印ivars

由此可见,成员变量和属性都在ivars里面,说到这里,就说一下成员变量和属性的区别在哪,我们打开底层的源码结构来看一看(clang 在我的第三篇文章中有讲到)

成员变量和属性区别
成员变量和属性区别

由图中可以看出成员变量和属性主要是两种区别
1、属性带下划线,成员变量不带下划线
2、属性会自动生成get和set方法

再补充一点, 什么是实例变量,顾名思义就是能够进行对象实例化的就是实例变量,比如说NSObject,UIView,UILabel等等,当然了,实例变量也是一种特殊的成员变量

接下来补充一张苹果底层的标识符合


标识符对应的意思

拿一句代码做个比喻大伙就明白了:{{(struct objc_ selector *)"nickName", "@16@0:8", (void *)_ I_ LGPerson_ nickName}

这里的@符号代表返回值; 16代表内存大小,共用了16个字节; 第二个@代表第一个参数 0 代表第一个参数从0号位置开始 :代表sel, 方法编号 8 代表方法的开始位置,从8号位置开始

除了sel 是方法编号(相当于书包目录的名称),还有imp函数指针地址(相当于书本目录的页码)

在讲面试题之前,再讲一下类方法为什么没有在rw里面的methods()里面,这也是上一篇文章中所提到的,讲完这一点就可以看面试题了

说到方法,其实方法的归属总是来源于类的,类里面只有实例方法而没有类方法,那肯定存在其它地方了,通过探索类方法在元类里面,我们来打印验证一下

拿到元类

首先拿到元类,然后调用data(),再调用methods(),接下来就是见证奇迹的时候了,我们把list打印出来

打印出类方法

此时此刻,我们成功的验证了类方法就在元类里面,类的结构分析我们就暂时先告一段落,接下来看两道经典的面试题巩固加深一下

两道经典面试题之第一道

定义一个Person类,代码如下:

Person类

然后我通过一些方法来验证LGPerson的方法是否存在于InstanceMethod、ClassMethod、MethodImplementation,通过验证打印出的结果,我用注释写的清清楚楚,明明白白,如图:

InstanceMethod是否存在方法
面试题

然后我们今天第一道面试题来了。 就是本来sayHappy在元类中以对象方法存在,为什么在元类里面还会以类方法的形式存在呢?

首先我们来看一下class_getClassMethod里面实现,然后跟着我图上标的三个步骤走

class_getClassMethod实现方法

从上幅图中可以看出得到一个类的类方法,就相当于得到一个元类的实例方法 然后我们再点到getMeta()里面瞅瞅写的啥

getMeta

看到这里,相信大部分都已经明白了,意思是如果是元类就可以返回了,所以给我们返回了元类的实例方法。 同时没有必要无限制递归下去了,因为一直找下去会找到根元类,然后根元类再找就是自己了,死循环了,可以看一下我第三篇中的isa走位图。

第二道面试题

面试题

看到这幅图大家先思考一下接下来会打印什么,然后再接着听我分析

我们先点到 isKindOfClass 源码看一下里面做了什么,发现有两个方法,一个是类方法, 一个是对象方法

isKindOfClass源码

我们先来看一下类方法里面做了什么事情


isKindOfClass类方法

用一句话概括就是先拿当前类和元类进行对比,然后再找元类的父类和当前类进行对比,知道这个原理后我们来分析下为什么re1 返回1

因为NSObject的元类不等于NSObject,元类的父类根元类,根元类是不等于NSObject的,根元类的父类是NSObject,所以NSObject等于NSObject返回YES

知道这个原因之后同理 re3 就很好验证了:

当前LGPerson类和元类是不相等,元类的父类是继承元类或者根元类,根元类的父类是NSObject,都是不等于LGPerson,所以返回NO

我们再来看下isKindOfClass的对象方法

isKindOfClass实例方法

isKindOfClass的实例方法就很好理解了,就是拿当前类的class和传入的类是不是相等,如果相等返回YES,不相等则递归找父类进行比较。

因为re5和re7 获取对象类 与 传入类对比是一致的,所以都不用找父类他们就是相等的,返回的肯定是1

事实上,这里有一个坑,经过断点调试发现不会走isKindOfClass,为什么呢?一般人都不知道,看了我的博客,我就告诉大家:这里主要是因为在llvm中编译时对其进行了优化处理

会走到汇编里面来

isKindOfClass汇编
真正的isKindOfClass源码分析

注意:这里才是真真正正isKindOfClass走的源码,前面都是在一本正经的胡说八道

图中已经写了isKindOfClass源码分析的注释,通过对比结果和上面的一致。

最后我们再来看一下isMemberOfClass 这个玩意,这个断点会走的,请大家放心

isMemberOfClass源码分析

看到这个源码,相信大家都秒懂了。弄明白我上面所说的,这里马上就能解释re2,re4,re6,re8 这几个家伙了

re2: NSObject的元类是根元类, 根元类是不等于NSObject这个类的,返回0

re4: LGPerson的元类和LGPerson类是不相等的,返回0

re6: NSObject的对象的类 和NSObject 是相等的,返回1

re8: LGPerson的对象的类 和LGPerson 是相等的,返回1

完事!

iOS 底层原理 文章汇总

你可能感兴趣的:(类结构分析下+经典面试题分析)