这是我参与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 方法。实现如下:
得出的结果依然是一样的。