iOS开发底层之类的底层探究下-06
Posted iOS_developer_zhong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之类的底层探究下-06相关的知识,希望对你有一定的参考价值。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
类底层探究
一、解决上篇博客遗留的问题?
1. 类方法藏在何处?
答案在本章节的第二部分的第4小点详细说明。 (往下翻)
二、成员变量(实例变量)与属性
1.把文件编译成cpp类型,看c++源码。
// 成员变量 vs 属性 VS 实例变量
@interface LGPerson : NSObject
{
// STRING int double float char bool
NSString *hobby; // 字符串
int a;
NSObject *objc; // 结构体
}
@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@end
- 通过观看底层代码,两者的区别如下:
- 属性:会自动生成带下划线的名称
- 属性还会自动生成get,set方法
看cpp代码留下了疑问? 为什么属性set方法有的是内存偏移,有的是objc_setProperty,后续在回到这个问题。
2.类型编码
#pragma mark - 各种类型编码 代码获取
void lgTypes(void){
NSLog(@"char --> %s",@encode(char));
NSLog(@"int --> %s",@encode(int));
NSLog(@"short --> %s",@encode(short));
NSLog(@"long --> %s",@encode(long));
NSLog(@"long long --> %s",@encode(long long));
NSLog(@"unsigned char --> %s",@encode(unsigned char));
NSLog(@"unsigned int --> %s",@encode(unsigned int));
NSLog(@"unsigned short --> %s",@encode(unsigned short));
NSLog(@"unsigned long --> %s",@encode(unsigned long long));
NSLog(@"float --> %s",@encode(float));
NSLog(@"bool --> %s",@encode(bool));
NSLog(@"void --> %s",@encode(void));
NSLog(@"char * --> %s",@encode(char *));
NSLog(@"id --> %s",@encode(id));
NSLog(@"Class --> %s",@encode(Class));
NSLog(@"SEL --> %s",@encode(SEL));
int array[] = {1,2,3};
NSLog(@"int[] --> %s",@encode(typeof(array)));
typedef struct person{
char *name;
int age;
}Person;
NSLog(@"struct --> %s",@encode(Person));
typedef union union_type{
char *name;
int a;
}Union;
NSLog(@"union --> %s",@encode(Union));
int a = 2;
int *b = {&a};
NSLog(@"int[] --> %s",@encode(typeof(b)));
}
学习苹果的简写方法:
如: @16@0:8
1| @ 代表 id
2| 16 代表 占用16个字节内存
3| @ 代表 id
4| 0 代表 从0号位置开始
5| : 代表 SEL
6| 8 代表从8号位置开始
苹果官方编码对照表
📢:类型编码图的获取途径:
打开xcode–> command+shift+0–> 搜索ivar_getTypeEncoding–> 点击Type Encodings
实用方法: 快速打印一个类的属性与实例变量? 来自逻辑教育 6的飞起老师
void lgObjc_copyIvar_copyProperies(Class pClass){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
const char*cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
LGLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
LGLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
3.setter方法的底层原理
- setter方法的底层原理简要描述
llvm源码下载
setName 和 setAge等属性的setter方法,如果一个属性对应一个setter方法,那就太低效了,所以,底层会封装一个通用的方法 objc_setProperty的方法来进行统一处理, 并进行绑定。
通过llvm底层有个重大发现,也是接上了这篇博客上面提出的问题?,为什么成员属性的setter方法有的是objc_setProperty,有的又是通过内存平移来实现setter方法的。 原来是和修饰的关键字有关系。
// CGObjC.cpp 858行
if (IsCopy) {
kind = GetSetProperty;
return;
}
- setter小结:
- nonatomic, copy修饰的属性使用objc_getProperty方式实现 。atomic, copy修饰的属性也是使用objc_getProperty方式实现。retain一般在MRC模式下使用,现在使用的基本是在ARC模式,retain这种修饰暂时忽略。
- 其他关键字修饰的属性, 如单 nonatomic, atomic, nonatomic , Strong 等,都是走的内存平移方式。
- 苹果针对setter方法写了个适配器的中间层,因为如果底层需要维护,修改起来特别麻烦。中间层的作用是供上层的setter调用,中间层对属性的修饰符进行判断走不同的流程,调用底层的方法实现(为底层提供策略。)
中间层的优点:底层变化上层不受影响,上层变化底层也不会受影响
4.类方法的底层原理(为了解决本篇文章头部的问题 lldb)
-
类方法到底隐藏在何处?
类方法是存放在元类的方法中。 -
继续实战操作
2.1. 获取当前类的元类操作
2.2. lldb 获取类方法的操作步骤,注意看注释
可以完美的知道类方法是藏在何处的。
(lldb) x/4gx LGPerson.class
0x100008408: 0x0000000100008430 0x000000010036a140
0x100008418: 0x00000001015065f0 0x0002802800000003
(lldb) p/x 0x0000000100008430 + 0x20 // 1. 此处是针对元类的偏移
(long) $1 = 0x0000000100008450
(lldb) p/x (class_data_bits_t *) 0x0000000100008450 // 2. 这个和前面的操作获取属性一模一样 ,转换成class_data_bits_t 对象
(class_data_bits_t *) $2 = 0x0000000100008450
(lldb) p/x $2->data(); // 3. 调用data()方法,获取class_rw_t对象
(class_rw_t *) $3 = 0x00000001015065b0
(lldb) p *$3
(class_rw_t) $4 = { // 4. 查看class_rw_t内部结构
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4295000824
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff800bdeb0
}
(lldb) p/x $4->methods(); //5. 获得method_array_t对象
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100008340
}
arrayAndFlag = 0x0000000100008340
}
}
}
Fix-it applied, fixed expression was:
$4.methods();
(lldb) p/x $5.list //6. 获得方法的列表 method_list_t
(const method_list_t_authed_ptr<method_list_t>) $6 = {
ptr = 0x0000000100008340
}
(lldb) p/x $6.ptr; // 7 得到内存指针
(method_list_t *const) $7 = 0x0000000100008340 method_t
(lldb) p *$7 // 8. 打印对象内部结构,得到、method_t对象
(method_list_t) $8 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $8.get(0).big(); //9. 打印方法的具体内容
(method_t::big) $9 = {
name = "sayNB"
types = 0x0000000100003f6a "v16@0:8"
imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}
(lldb)
5. 类方法与实例方法代码分析
-
概念:在底层没有所谓的类方法与实例方法,只有对象方法。
-
获取类的方法列表代码段
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);
}
- 下面通过一个案例来玩下实例方法与类方法。
@interface LGPerson : NSObject
{
NSObject *objc;
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;
- (void)sayHello;
+ (void)sayHappy;
@end
3.1 我们通过实例方法来对比下类和元类中的类方法与实例方法
结论: sayHello为实例方法这个我们清楚, 但sayHappy明明是个类方法, 为何打印元类中的实例方法能打印出值, 这就是为什么强调在底层是没有实例与类方法之分的, 其实sayHappy方法就是一个实例方法,放在了元类而已。
3.2 我们通过类方法来对比下类和元类中的类方法与实例方法
结论: sayHello为实例方法,所以打印不出来, sayHappy是类方法,类与元类都可以查询到。
3.3 我们通过类方法SEL 获取下 IMP 来实现下类和元类中的IM区别。
结论: 这里很奇怪,为啥 sayHello在元类是没有实现的,还返回了地址,同理 sayHappy方法也没有在类中实现,也返回了地址,还巧合的两个地址相同,这是为什么呢? 看源码一目了然。 在OBJC源码中,搜索 class_getMethodImplementation
原来底层处理为找不到对应== IMP ==,就指定返回一个 _objc_msgForward的函数,所以上面返回了相同的函数指针地址。
补充
1. objc_object 和 objc_class,person 和 NSObject 的联系?
解释: person是oc层面的一个对象, 底层就是一个结构体对象为 objc_object, 同理, NSObject为OC层面的一个对象, 底层对应的结构体对象为 objc_class 。
2. id 是什么?
解释: id是 objc_object的一个指针
3. class 是什么?
解释: class就是objc_class的一个指针
以上是关于iOS开发底层之类的底层探究下-06的主要内容,如果未能解决你的问题,请参考以下文章
iOS开发底层之RuntimeObjc_msgSend探究下 - 09