第6课-类的原理分析下
[TOC]
6.1 成员变量、属性的区别以及编码
6.1.1 成员变量、属性和实例变量
- 属性(property):在OC中是通过@property开头定义,且是带下划线成员变量 + setter + getter方法的变量
- 成员变量(ivar):在OC的类中{}中定义的变量
- 实例变量:是一种特殊的成员变量,它的类型为对象,例如 NSObject、UILabel、UIButton等
我们通过一个例子,看一下底层源码:
// 成员变量 vs 属性 VS 实例变量
@interface ZBPerson : NSObject
{
NSString *string; // 字符串
NSObject *objc; // NSObject
int a;
}
@property (nonatomic, copy) NSString *nonatomicCopyName;
@property (atomic, copy) NSString *atomicCopyName;
@property (nonatomic, strong) NSString *nonatomicStrongName;
@property (atomic, strong) NSString *atomicStrongName;
@property (nonatomic) NSString *nonatomicName;
@property (atomic) NSString *atomicName;
@end
@implementation ZBPerson
@end
上述代码我们通过clang编译器编译之后看一下源码
我们可以看到,属性经过编译之后会生成下划线开头的成员变量。
同样生成了对应的getter和setter方法
所以我们这里也验证了属性在底层会自动生成成员变量,getter以及setter方法。
我们再看编译之后的代码,发现有个_method_list_t结构体,里面有@16@0:8这样的结构,这是什么东西呢?这就是类型编码。
6.1.2 类型编码
我们可以查看苹果源文档
- 通过快捷键 command+shift+0
打开之后我们搜索 ivar_getTypeEncoding
点击底部的Type Encodings
那么我们来分析一下上面提到的 {{(struct objc_selector *)"nonatomicCopyName", "@16@0:8", (void *)_I_ZBPerson_nonatomicCopyName},
@16@0:8的含义,我们从左向右依次分析 补充: OC所有的方法底层都有默认的两个参数(id SELF, SEL _cmd),SELF是id类型,_cmd是SEL类型
- @: id,方法返回值类型
- 16:方法总计占用内存16字节大小
- @:id类型,表示第一个参数是id类型,也就是SELF
- 0:从第0号位置开始
- : SEL类型,第2个参数
- 8:从第8号位置开始
- 通过上面的分析,我们也可以得出id和SEL类型各占用8字节
接下我们再分析一下{(struct objc_selector *)"setNonatomicCopyName:", "v24@0:8@16", (void *)_I_ZBPerson_setNonatomicCopyName_}
v24@0:8@16的含义
- v: void返回值类型
- 24:方法总计占用24字节内存
- @:id类型 SELF
- 0:从第0号位置开始
- : SEL类型,第2个参数
- 8:从第8号位置开始
- @:id类型,第3个参数,这里也就是nonatomicCopyName
- 16:从第16号位置开始
- 通过上面的分析,我们也可以得出三个参数各占用8字节内存
我们也可以通过代码@encode(类型)来验证一下,代码如下:
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));
NSLog(@"unsigned long 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(@"*b --> %s",@encode(typeof(b)));
// 打印结果如下:
2022-03-28 18:07:49.890271+0800 001-类的属性与变量[30803:1285544] char --> c
2022-03-28 18:07:50.100018+0800 001-类的属性与变量[30803:1285544] int --> i
2022-03-28 18:07:50.282418+0800 001-类的属性与变量[30803:1285544] short --> s
2022-03-28 18:07:50.442694+0800 001-类的属性与变量[30803:1285544] long --> q
2022-03-28 18:07:50.623197+0800 001-类的属性与变量[30803:1285544] long long --> q
2022-03-28 18:07:50.817771+0800 001-类的属性与变量[30803:1285544] unsigned char --> C
2022-03-28 18:07:51.018078+0800 001-类的属性与变量[30803:1285544] unsigned int --> I
2022-03-28 18:07:51.203710+0800 001-类的属性与变量[30803:1285544] unsigned short --> S
2022-03-28 18:07:51.367705+0800 001-类的属性与变量[30803:1285544] unsigned long --> Q
2022-03-28 18:07:51.553251+0800 001-类的属性与变量[30803:1285544] unsigned long long --> Q
2022-03-28 18:07:51.747663+0800 001-类的属性与变量[30803:1285544] float --> f
2022-03-28 18:07:51.918496+0800 001-类的属性与变量[30803:1285544] bool --> B
2022-03-28 18:07:52.123806+0800 001-类的属性与变量[30803:1285544] void --> v
2022-03-28 18:07:52.343834+0800 001-类的属性与变量[30803:1285544] char * --> *
2022-03-28 18:07:52.558203+0800 001-类的属性与变量[30803:1285544] id --> @
2022-03-28 18:07:52.817783+0800 001-类的属性与变量[30803:1285544] Class --> #
2022-03-28 18:07:52.998731+0800 001-类的属性与变量[30803:1285544] SEL --> :
2022-03-28 18:07:53.388577+0800 001-类的属性与变量[30803:1285544] int[] --> [3i]
2022-03-28 18:07:53.567204+0800 001-类的属性与变量[30803:1285544] struct --> {person=*i}
2022-03-28 18:07:53.772738+0800 001-类的属性与变量[30803:1285544] union --> (union_type=*i)
2022-03-28 18:07:56.281749+0800 001-类的属性与变量[30803:1285544] *b --> ^i
需要注意一点long和unsigned long的编码都是Q
6.3 setter方法的底层原理
源码经过编译之后生成的get方法如下:
我们发现生成的setter方法有两种实现形式:
- 通过内存平移的形式来实现,首地址+偏移量,例如
static void _I_ZBPerson_setNonatomicStrongName_(ZBPerson * self, SEL _cmd, NSString *nonatomicStrongName) { (*(NSString **)((char *)self + OBJC_IVAR_$_ZBPerson$_nonatomicStrongName)) = nonatomicStrongName; }
- 通过objc_setProperty方法的形式来实现,例如
static void _I_ZBPerson_setAtomicCopyName_(ZBPerson * self, SEL _cmd, NSString *atomicCopyName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ZBPerson, _atomicCopyName), (id)atomicCopyName, 1, 1); }
为什么会使用objc_setProperty这种形式呢?因为源码经过编译之后才有的objc_setProperty方法,所以我们初步怀疑是编译器帮我们做的处理。接下来我们查看一下编译器的源码,在源码中搜索这个方法。 我们发现了如下代码 通过名字我们可以知道CodeGenFunction::generateObjCSetterBody这个函数应该是生成setter方法的,switch分支对不同类型做了区分,这里的PropertyImplStrategy尤为关键,我们再查看一下它的源码
可以发现如果是GetSetProperty或者SetPropertyAndExpressionGet类型就会生成objc_setProperty方法。那么我们看一下PropertyImplStrategy它是如何初始化的。
关键代码 1:如果是Copy 那么必然使用SetProperty,至于Get 则根据是否是原子性判断。 关键代码 2:如果不是Copy,则根据原子性来判断,当使用原子性时,则会使用SetProperty
我们找到了最关键的代码,isCopy,也就是说如果属性使用了copy修饰,那么就会生成objc_setProperty方法,再来检查一下源码,确是使用copy修饰的属性才生成了objc_setProperty代码。
结论:如果属性中使用了copy修饰,那么编译器就会将属性的setter方法通过objc_setProperty来实现。之所以需要对copy特别处理,是因为使用copy修饰的成员变量,在赋值的时候,需要重新拷贝一份新的内存,涉及到新内存的开辟,所以需要特别处理。另外使用copy修饰的属性有很多,如果单独为每一个属性生成一个新的setter方法也没必要,所以这里也进行了一层封装,所有使用copy修饰的setter方法内部都会通过objc_setProperty来实现,然后再通过objc_setProperty具体分发执行。同样objc_getProperty也是同样的道理,是底层编译器做了优化