iOS 底层探索篇 —— 类的原理分析-下

成员变量 & 属性 & 实例变量

  • 属性(property):在OC中是通过@property开头定义,且是带下划线成员变量 + setter + getter方法的变量

  • 成员变量(ivar):在OC的类中{}中定义的,为基本数据类型,且没有下划线的变量

  • 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量,例如 NSObject、UILabel、UIButton或者自己创建的类等
    在这里插入图片描述
    那么在我们的类中,哪些是成员变量,哪些是属性,哪些是实例变量呢根据上文的解释我们知道:

  • 属性:nickName,acnickName,nnickName,anickName,name,aname。

  • 成员变量: hobby,a。

  • 实例变量: objc。

我们在clang一下main文件, 探索一下底层结构来看一下属性和成员变量的关系
在这里插入图片描述
打开main.cpp文件,然后找到我们的LGPerson类。在这里插入图片描述

我们看到,属性在cpp文件中,属性都没有用了。他会在cpp中被优化成带下划线的成员变量并且自动生成gettersetter
在这里插入图片描述

在这里插入图片描述
我们注意到,为什么有的set方法是用objc_setProperty,有的确是通过内存平移赋值,有得get方法是通过objc_getProperty,有的确是通过内存平移取值呢。

我们想,所有的setter几乎都是同样的工作,就是赋值到一个内存区域,。如果我们每一个setter都要一个底层实现,那就太繁琐了,于是苹果就在底部封装来基类的方法。然后在中间创建了一个方法叫做objc_setProperty,底层针对objc_setProperty进行相应的底层代码实现,setter 通过中间方法objc_setProperty去调用基类方法。

接下来我们打开llvm,然后搜索objc_setProperty
在这里插入图片描述
我们看到这里有个创建并返回objc_setProperty方法的地方。
在这里插入图片描述

在看到这个方法的名字叫做getSetPropertyFn,也就是这个方法是要去获得setproperty方法的,我们在llvm 中搜索这个方法。
在这里插入图片描述
发现是个中间层代码,我们改为搜索GetPropertySetFunction
在这里插入图片描述

然后我们发现了这个函数的调用,并且发现当PropertyImplStrategySetPropertyAndExpressionGetGetSetProperty才会调用这个方法。
在这里插入图片描述
我们往上翻,看到了我们是去判断strategy这个实例变量的种类,而strategy这个实例变量是根据PropertyImplStrategy的类型搞过来的。所以我们需要去寻找,哪里有对PropertyImplStrategy进行赋值。因此,我们在llvm 中寻找PropertyImplStrategy实在那里初始化的。
在这里插入图片描述
我们发现了这里进行了初始化,然后我们点进去这个方法。
在这里插入图片描述
这里发现,当有copy的时候,PropertyImplStrategy就会被复制为GetSetProperty属性。也就是说copy是决定是不是调用objc_setProperty方法的决定条件。我们来验证一下。
在这里插入图片描述在cpp文件中发现,确实只有添加了copy后,才会调用set_property方法,其他的是通过内存平移进行赋值的。

类方法

lldm证明

在 iOS 底层探索篇 —— 类的原理分析-上中,我们发现类方法并不在类的方法列表中,那么他究竟在哪里呢。在这里插入图片描述

我们在LGPerson中添加一个类方法say666
如果把对象方法和类方法都放在类中,那么如果他们同名的话就无法找到。所以苹果想到一个办法,那就是创建元类并把类方法存放在元类中。
来验证一下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

runtime证明

  • lgObjc_copyMethodList

在这里插入图片描述
我们先创建一个方法用来打印class的所有方法。
在这里插入图片描述
然后将类以及元类分别传入进去,然后来看我们的打印结果。
在这里插入图片描述
我们发现,say666果然存在在元类中。

  • lgInstanceMethod_classToMetaclass

在这里插入图片描述
在这里插入图片描述
类方法对元类来说,就是一个对象方法,所以我们可以看到,这里证明了对象方法在类里面,而类方法在元类里面。

  • lgClassMethod_classToMetaclass

在这里插入图片描述
在这里插入图片描述
这里的输出就比较奇怪了,对象方法在类和元类中都没有,而类方法在类和元类中都有。我们来看一下class_getClassMethod在底层是如何实现的。
在这里插入图片描述
我们看到,这里其实还是调用获取对象方法的方法,但是对cls进行了处理。我们再来看一下getMeta这个方法。
在这里插入图片描述
我们发现,这个方法会判断传进来的类是不是元类是的话就返回自己,不是的话就返回isa,我们知道类的isa指向他的元类,所以返回isa也就是返回类的元类
因此,用这个方法打印对象方法都是空的,而类方法在两个class中都能打印。

  • lgIMP_classToMetaclass

在这里插入图片描述
在这里插入图片描述
在这个方法中,我们发现元类找对象方法的imp,类找类方法竟然是有值的,而且他们的值是一样的。这是为什么呢。
遇事不决问源码,在源码中我们搜索class_getMethodImplementation是如何实现的
在这里插入图片描述
在途中我们看到,如果imp为空的话,class_getMethodImplementation方法会返回_objc_msgForward,这也就解释了为什么那两个imp有值而且值是一样的。

Type encoding

我们在main.cpp看到一些奇奇怪怪的符号:
在这里插入图片描述
这个其实是类型编码(TypeEncodings),类型编码的作用是为了协助运行时系统,编译器用字符串为每个方法的返回值参数类型方法选择器编码.就是编译器内部把每个方法的返回值,参数类型和方法选择器->用特定的字符代替.
我们可以通过一些步骤找到它:
我们打开XCodehelp, 然后打开deveoper documentation,或者直接打开xcode 后按下command + option + 0打开 developer documentation,然后在developer documentation 中搜索 ivar_getTypeEncoding
在这里插入图片描述
然后我们点击一下这个type encodings,系统就会跳转到苹果的官方文档,然后我们就可以看到这个type encodings 的表格啦。

在这里插入图片描述
知道了这些是啥之后,我们继续往下翻。我们又看到一些奇怪的东西。
在这里插入图片描述
根据方法名字我们可以知道,这个是对象方法列表,但是红色框框里奇奇乖乖的符号是什么意思呢?
其中的那些符号是类型编码,那么数字又是什么东西呢。
我举个🌰:
我们以@16@0:8为例子

  • @: 返回值为@,根据 类型编码表中找到,@代表了id
  • 16: 代表占总共占用的字节数16字节
  • @:第一个参数为@,也就是id
  • 0: 第一个参数从0位开始占用8个字节
  • :第二个参数为:,根据 类型编码表中找到,:代表了SEL
  • 8: 第二个参数从8位开始占用8个字节

再举一个🌰:
v24@0:8@16

  • V: 返回值为V,根据 类型编码表中找到,v代表了void
  • 24: 代表占总共占用的字节数24字节
  • @:第一个参数为@,也就是id
  • 0: 第一个参数从0位开始占用8个字节
  • :第二个参数为:,根据 类型编码表中找到,:代表了SEL
  • 8: 第二个参数从8位开始占用8个字节
    • @:第三个参数为@,也就是id
  • 16: 第三个参数从16位开始占用8个字节

面试题:isKindOfClass / isMemberOfClass

题目:
在这里插入图片描述

方法:
在这里插入图片描述
分析:

  1. BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

这里调用的是 isKindOfClass类方法,根据上面的方法来看,cls是NSObject类

  • 第一次循环时进去时tcls 等于NSObject->ISA()也就是NSObject元类,所以tcls!=cls
  • 接着第二次循环时,tcls = tcls->getSuperclass(),也就是NSObject元类父类也就是NSObject 类, 所以 tcls == cls,返回true.
  1. BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
  • 这里调用的是 isMemberOfClass的类方法,根据上面的方法来看,cls是NSObject类self->ISA()也就是 NSObject 类的isa 指向的是NSObject的元类, 所以 return self->ISA() == cls 是返回false
  1. BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
  • 这里调用的是 isKindOfClass的类方法,根据上面的方法来看,cls是LGPerson类
  • 次循环时进去时tcls 等于LGPerson->ISA()也就是LGPerson元类,所以tcls!=cls
  • 接着第次循环时,tcls = tcls->getSuperclass(),也就是LGPerson元类父类也就是NSObject 元类, 所以 tcls != cls
  • 接着第次循环时,tcls = tcls->getSuperclass(),也就是NSObject元类父类也就是NSObject 类, 所以 tcls != cls
  • 次循环时,NSObject类的父类为空,所以tcls为空,结束循环,返回false
  1. BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
  • 这里调用的是 isMemberOfClass的类方法,根据上面的方法来看,cls是LGPerson类self->ISA()也就是 LGPerson 类的isa 指向的是LGPerson的元类, 所以 return self->ISA() == cls 是返回false
  1. BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
  • 这里调用的是 isKindOfClass对象方法,根据上面的方法来看,cls是NSObject类
  • 第一次循环时进去时tcls 等于[self class],NSObject对象的类也就是NSObject类,所以tcls==cls,返回true.
  1. BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
  • 这里调用的是 isMemberOfClass对象方法,根据上面的方法来看,cls是NSObject类
  • NSObject 对象的 class 是NSObject 类,所以 [self class] == cls ,返回 true
  1. BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
  • 这里调用的是 isKindOfClass对象方法,根据上面的方法来看,cls是LGPerson类
  • 第一次循环时进去时tcls 等于[self class],LGPerson对象的类也就是LGPerson类,所以tcls==cls,返回true.
  1. BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
  • 这里调用的是 isMemberOfClass对象方法,根据上面的方法来看,cls是LGPerson类
  • LGPerson 对象的 class 是LGPerson 类,所以 [self class] == cls ,返回 true

输出验证一下:
在这里插入图片描述
坑点:
在这里插入图片描述

根据汇编得知这里有个坑点:实际上isKindOfClass 调用的是objc_opt_isKindOfClass 方法。实现如下:

在这里插入图片描述
得出的结果依然是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值