第6课-类的原理分析下

本文详细解析了Objective-C中属性、成员变量和实例变量的区别,并通过源码分析了属性在底层如何生成成员变量及getter、setter方法。同时,介绍了类型编码的含义,包括方法参数的内存布局和类型编码的表示。最后,探讨了setter方法的两种实现形式,特别是对于使用`copy`修饰的属性,编译器如何通过`objc_setProperty`进行优化处理。
摘要由CSDN通过智能技术生成

第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方法有两种实现形式:

  1. 通过内存平移的形式来实现,首地址+偏移量,例如static void _I_ZBPerson_setNonatomicStrongName_(ZBPerson * self, SEL _cmd, NSString *nonatomicStrongName) { (*(NSString **)((char *)self + OBJC_IVAR_$_ZBPerson$_nonatomicStrongName)) = nonatomicStrongName; }
  2. 通过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也是同样的道理,是底层编译器做了优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值