这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
成员变量 & 属性 & 实例变量
-
属性(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中被优化成带下划线
的成员变量并且自动
生成getter
和setter
。
我们注意到,为什么有的set方法
是用objc_setProperty
,有的确是通过内存平移赋值
,有得get方法是通过objc_getProperty
,有的确是通过内存平移取值呢。
我们想,所有的setter几乎都是同样的工作,就是赋值到一个内存区域,。如果我们每一个setter都要一个底层实现,那就太繁琐了,于是苹果就在底部封装来基类的方法。然后在中间创建了一个方法叫做objc_setProperty,底层针对objc_setProperty进行相应的底层代码实现,setter 通过中间方法objc_setProperty去调用基类方法。
接下来我们打开llvm
,然后搜索objc_setProperty
。
我们看到这里有个创建并返回objc_setProperty
方法的地方。
在看到这个方法的名字叫做getSetPropertyFn
,也就是这个方法是要去获得setproperty
方法的,我们在llvm 中搜索这个方法。
发现是个中间层代码,我们改为搜索GetPropertySetFunction
。
然后我们发现了这个函数的调用,并且发现当PropertyImplStrategy
为SetPropertyAndExpressionGet
和GetSetProperty
才会调用这个方法。
我们往上翻,看到了我们是去判断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)
,类型编码的作用是为了协助运行时系统,编译器
用字符串为每个方法的返回值
和参数类型
和方法选择器
编码.就是编译器内部把每个方法的返回值,参数类型和方法选择器->用特定的字符
代替.
我们可以通过一些步骤找到它:
我们打开XCode
的help
, 然后打开deveoper documentation
,或者直接打开xcode 后按下command + option + 0
打开 developer documentation,然后在developer documentation 中搜索 ivar_getTypeEncoding
。
然后我们点击一下这个type encodings
,系统就会跳转到苹果的官方文档,然后我们就可以看到这个type encodings 的表格啦。
知道了这些是啥之后,我们继续往下翻。我们又看到一些奇怪的东西。
根据方法名字我们可以知道,这个是对象方法列表,但是红色框框里奇奇乖乖的符号是什么意思呢?
其中的那些符号是类型编码,那么数字又是什么东西呢。
我举个?:
我们以@16@0:8为例子
@
: 返回值为@,根据 类型编码表中找到,@代表了id16
: 代表占总共占用的字节数16字节@
:第一个参数为@,也就是id0
: 第一个参数从0位开始占用8个字节:
:第二个参数为:,根据 类型编码表中找到,:代表了SEL8
: 第二个参数从8位开始占用8个字节
再举一个?:
v24@0:8@16
V
: 返回值为V,根据 类型编码表中找到,v代表了void24
: 代表占总共占用的字节数24字节@
:第一个参数为@,也就是id0
: 第一个参数从0位开始占用8个字节:
:第二个参数为:,根据 类型编码表中找到,:代表了SEL8
: 第二个参数从8位开始占用8个字节-
@
:第三个参数为@,也就是id
16
: 第三个参数从16位开始占用8个字节
面试题:isKindOfClass / isMemberOfClass
题目:
方法:
分析:
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
.
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
- 这里调用的是 isMemberOfClass的类方法,根据上面的方法来看,
cls是NSObject类
,self->ISA()
也就是 NSObject 类的isa 指向的是NSObject的元类
, 所以 return self->ISA() == cls 是返回false
。
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
。
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
- 这里调用的是 isMemberOfClass的类方法,根据上面的方法来看,
cls是LGPerson类
,self->ISA()
也就是 LGPerson 类的isa 指向的是LGPerson的元类
, 所以 return self->ISA() == cls 是返回false
。
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
- 这里调用的是
isKindOfClass
的对象方法
,根据上面的方法来看,cls是NSObject类
。 - 第一次循环时进去时
tcls 等于[self class],NSObject对象的类也就是NSObject类
,所以tcls==cls
,返回true
.
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
- 这里调用的是
isMemberOfClass
的对象方法
,根据上面的方法来看,cls是NSObject类
。 NSObject 对象的 class 是NSObject 类
,所以 [self class] == cls ,返回 true
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
- 这里调用的是
isKindOfClass
的对象方法
,根据上面的方法来看,cls是LGPerson类
。 - 第一次循环时进去时
tcls 等于[self class],LGPerson对象的类也就是LGPerson类
,所以tcls==cls
,返回true
.
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
- 这里调用的是
isMemberOfClass
的对象方法
,根据上面的方法来看,cls是LGPerson类
。 LGPerson 对象的 class 是LGPerson 类
,所以 [self class] == cls ,返回 true
输出验证一下:
坑点:
根据汇编得知这里有个坑点:实际上isKindOfClass 调用的是objc_opt_isKindOfClass 方法。实现如下:
得出的结果依然是一样的。