iOS底层探索之类的结构(下)
Posted 卡卡西Sensei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层探索之类的结构(下)相关的知识,希望对你有一定的参考价值。
在之前的几篇博客里面,已经介绍了,类的底层结构,还有isa的走位流程,元类的继承链,对象方法,类方法的存放位置,以及如何通过lldb
获取成员和属性、对象方法,类方法。
iOS底层探索之类的结构(上)
iOS底层探索之类的结构(中)
本篇博客主要做一些补充和扩展。
成员变量和属性
开发年限比较久的ios程序员,都知道在iOS5
之前,经常看到一个大括号里面定义了成员变量,同时用了@property
声明,而且还在@implementation
中使用@synthesize
方法。
其实,发生这种状况根本原因是苹果将默认编译器从GCC
转换为LLVM
(low level virtual machine),才不再需要为属性声明实例变量了。
在没有更改之前,属性的正常写法需要成员变量+ @property + @synthesize
成员变量三个步骤。 更换为LLVM
之后,编译器在编译过程中发现没有新的实例变量后,就会生成一个下划线开头的实例变量。因此现在我们不必在声明一个实例变量。
现在@property
声明的属性不仅仅默认给我们生成一个_类型
的成员变量,同时也会生成setter/getter
方法。
- 属性 = 带下划线成员变量 + setter + getter ⽅法
- 实例变量 : 特殊的成员变量 (类的实例化)
从以上clang
底层源码也可以验证,@property
声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter
方法。
objc_setProperty
但是我们从上面的底层源码发现了一个问题,有的set
方法里面是通过objc_setProperty
获取属性,有的是通过内存的平移。这是什么原因了呢?通过对比不同属性设置带有copy
和不带有copy
的底层源码分析,可以找到原因是copy
这个设置的问题!那么为验证这个结论,我们去底层看看!
LLVM
那我们去llvm
源码里面看看,objc_setProperty
的实现逻辑,你怎么就知道去llvm不是去objc源码看呢?请看下图分析
首先在底层也就是下层,苹果的源码是固定的,是不会改变的(这里的不变是指不会根据你代码来改变,因为底层逻辑已经写好了)。在上层,我们程序开发人员,会写很多中属性,那么就会有各种各种的奇奇怪怪的代码出现,就有各种的set
形式的代码,比如setName、setAge、setXX
。。。等等。
这对编译器来说,只是_cmd
的方法名字不一样,那就需要一个中间层来把这些奇奇怪怪的,做一下处理,以便于和底层去交互,因为底层逻辑已经封装好,不可能动态的改变的。那我们去研究objc_setProperty
就不可能在运行时了,只能在编译时了,所以我们去就llvm
里面看看!
那上层和objc_setProperty
之间有着怎样的联系呢?
我们在llvm
里面找到了getGetPropertyFn
这个函数,是获取创建的CreateRuntimeFunction
,里面有objc_setProperty
这个参数。那么为什么创建呢?继续往下看看,在哪里调用了
既然调用了,肯定是有条件的,是真的是是因为property
的copy
吗?我们找到条件,就可以反推知道objc_setProperty
的作用了。继续往下看
看看上面的代码有没有发现什么?这有个属性imp的策略PropertyImplStrategy
,那么再继续往下找
看到没有,如果IsCopy
为真就Kind = GetSetProperty
,这也就验证了博客开篇的猜想,只有属性里面用copy修饰的,才会有objc_setProperty
。
代码获取类的信息
iOS底层探索之类的结构(上)
iOS底层探索之类的结构(中)
在之前的博客里面,我们已经知道了,可以在控制台通过lldb
获取类的信息,那么现在我们通过代码来获取下。
class_copyMethodList
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
class_getInstanceMethod
//获取实例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
class_getClassMethod
//获取类方法
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
class_getMethodImplementation
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
// sel -> imp 方法的查找流程 imp_farw
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
代码测试验证
IMP,SEL
- SEL : 类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。
- IMP:一个函数指针,保存了方法的地址
- SEL和IMP的关系就:SEL就相当于书本的⽬录标题,IMP就是书本的⻚码,函数就是具体页码对应的内容。
扩展
我们已经学了这么多类的相关的知识了,那么我们看看下面这个面试题
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[JPPerson class] isKindOfClass:[JPPerson class]];
BOOL re4 = [(id)[JPPerson class] isMemberOfClass:[JPPerson class]];
NSLog(@" re1 :%hhd\\n re2 :%hhd\\n re3 :%hhd\\n re4 :%hhd\\n,%hhd,%hhd",re1,re2,re3,re4,re9,re10);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[JPPerson alloc] isKindOfClass:[JPPerson class]];
BOOL re8 = [(id)[JPPerson alloc] isMemberOfClass:[JPPerson class]];
NSLog(@" re5 :%hhd\\n re6 :%hhd\\n re7 :%hhd\\n re8 :%hhd\\n",re5,re6,re7,re8);
代码运行结果
+ isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
+ isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (Class)class {
return object_getClass(self);
}
分析
re1
传入的是NSObject
的类,获取元类
与NSObject
不等,继续寻找获取元类的父类为NSObject
与传入的值相等,返回true
- re2是
NSObject
类调用类方法isMemberOfClass
与NSObject类
比较,很明显,NSObject的元类
与NSObject
本身并不相等,所以返回false
re3
传入的是JPPerson
的类,获取元类
与JPPerson
不等,继续寻找获取元类的父类为NSObject
的元类,与传入的值依旧不等,继续往上NSObject元类
的父类为NSObject
依旧不等,再往上就是nil
,最后返回false
-re4
是JPPerson
类调用类方法isMemberOfClass
与JPPerson类
比较,JPPerson的元类
与JPPerson
本身并不相等,所以返回false.re5
传入的是NSObject
的实例,获取对象的类,与NSObject
相等,返回true
re7
传入的是JPPerson
的实例,获取对象的类,与JPPerson
相等,返回true
-re8
是JPPerson
的实例调用实例方法isMemberOfClass
与JPPerson
类比较,明显的他们是相同的,所以返回true
更多内容持续更新
🌹 请动动你的小手,点个赞👍🌹
🌹 喜欢的可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我,哈哈😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
以上是关于iOS底层探索之类的结构(下)的主要内容,如果未能解决你的问题,请参考以下文章