【OC】属性关键字
文章目录
- 前言
- 属性关键字
- @property
- @synthesize
- @dynamic
- 原子操作
- atomic
- nonatomic
- 读写权限
- readwrite
- readonly
- 内存管理
- weak
- assign
- retain
- strong
- copy
- copy和strong对比
- 深浅拷贝
- 容器类对象的深拷贝
- 完全深拷贝
- 递归深拷贝
- 总结
前言
属性关键字是oc中基础且相当重要的知识点,之前学的时候有所接触但学的没有那么深入,这次便深入介绍一下其相关内容
属性关键字
@property
用来声明属性,编译器会默认:
- 自动生成一个实例变量(ivar),名字为**_属性名**
- 自动生成对应的getter/seter方法的声明与实现
@synthesize
以前必须写这个才能生成该实例变量与对应的方法,现在现在只需要经过@property声明,编译器便可以自动合成实例变量和方法。
- 现在可以通过这个来自定义实例变量的名字
@synthesize name = _myName; // 属性name对应的ivar叫_myName
@dynamic
这个是让我们的编译器不用自动进行@synthesize,其不会影响到@property所自动生成的getter,setter方法的声明。
编译器会自动帮我们生成实例变量和存取方法,但是还有几个得写@synthesize的例外:
- 如果我们自己实现了getter和setter,就不会自动帮我们生成,若我们想要一个ivar,就需要@synthesize
- 若该属性为readonly,且我们自己写了getter,若我们想要一个ivar,就需要@synthesize
- 在协议里的属性,协议只声明,不会生成ivar,所以在遵循协议的类里,若我们要ivar,则必须要@synthesize,不写@synthesize,就必须自己实现setter与getter,否则编译报错
原子操作
属性是否有原子性可以理解为线程是否安全
atomic
原子性,加同步锁,是默认的修饰符,用这个会消耗性能,仅可以保证单词访问是原子的,且不一定能保证线程安全,若想保证线程安全,可以用其他锁的机制(此处涉及到了多线程的部分知识,以后学习之后再进行补写)
nonatomic
非原子性,无同步保护,声明属性时基本为这个,可以提高访问性能
读写权限
默认为readwrite
readwrite
可读可写,属性拥有setter和getter方法
readonly
只读,仅提供了getter方法
内存管理
weak
只可用来修饰对象类型,ARC下才可以使用来修饰弱引用,不增加所修饰对象的引用计数,主要可以用来避免循环引用,所修饰的对象被释放后,会自动将指针指向nil,不会产生悬垂指针(即指向已被释放或者无效的指针)
assign
一般用于修饰基本数据类型(也可以用来修饰对象,但是在销毁时可能会产生悬垂指针,从而出现crash,即程序崩溃),setter方法是实现直接赋值,修饰如NSInteger,BOOL,int,float等的基本数据类型,且使用其修饰对象时时也不增加引用计数
retain
在MRC下使用,现在ARC下基本使用strong,主要用来修饰强引用,会增加引用计数,原理是用release释放之前的旧值,并调用retain,引用计数+1,最后再设置新值把原指针指向新的对象,现在基本不用所以不给出详解
strong
在ARC下使用,用来修饰强引用,原理与retain一样,但是ARC下编译器帮我们做了retain+release的操作
在修饰block时,strong相当于copy,retain相当于assign,原理如下:
block在内存中区域比较独特,在创建时其位于栈区,是临时的,函数返回后即会被销毁,但是在copy或者ARC下的strong修饰后会把block从栈区拷贝到堆区,类似于普通对象一样,可受到引用计数的管理,且只要强引用持有,其就会一直存在
copy
指定属性为拷贝对象的引用,而不是引用所拷贝的原始的对象
copy和strong对比
copy:深拷贝,内存地址不同,指针地址不同,release旧值,copy新值
strong:浅拷贝,内存地址不变指针地址不同
若声明的属性用的是copy,在合成方法时会使用类的copy方法,所以在此时,即便用的是可变的实例变量,生成的也会是一个不可变的副本,确保了对象不会无意间的被改动
深浅拷贝
对象复制及其中的深浅拷贝的内容在之前已经有所讲解,【oc】Foudation框架–字符串,对象复制详解,但是讲解的有很多当时没有学习的东西所以在这里进行部分补充,这里先展示之前的介绍的相当全面的一张表格:
被拷贝的对象类型 | 拷贝方法 | 是否新容器/新对象 | 是否复制内部元素 | 拷贝类型 |
---|---|---|---|---|
非容器类的不可变对象 | copy | 否(返回自身) | - | 浅拷贝 |
非容器类的不可变对象 | mutableCopy | 是 | 是(新对象) | 深拷贝 |
非容器类的可变对象 | copy | 是(不可变新对象) | 是(新对象) | 深拷贝 |
非容器类的可变对象 | mutableCopy | 是(可变新对象) | 是(新对象) | 深拷贝 |
容器类的不可变对象 | copy | 否(返回自身) | ❌(不复制元素) | 浅拷贝 |
容器类的不可变对象 | mutableCopy | 是(新容器) | ❌(不复制元素) | 深拷贝(容器里元素为浅层) |
容器类的可变对象 | copy | 是(不可变新容器) | ❌(不复制元素) | 深拷贝(容器里元素为浅层) |
容器类的可变对象 | mutableCopy | 是(新容器) | ❌(不复制元素) | 深拷贝(容器里元素为浅层) |
先回顾一下浅拷贝本质指的就是创建一个存放与所复制对象的地址相同的指针变量(固也称为指针拷贝),深拷贝指的是创建一个与被复制对象的值完全相同的对象,且他们的地址也不同,所以也叫做内容拷贝
容器类对象的深拷贝
在上面的表格中我们能看出,对容器类进行深拷贝时都只是进行了单层的深拷贝,即只是对容器进行了深拷贝了一份新的内存,但是里面的元素都是浅拷贝,未复制元素本身的内容,于是就有了下面这两种真正的深拷贝
完全深拷贝
对容器本身拷贝一份,且容器里的元素也一一拷贝,使用initWithXxxx: copyItems: YES方法
代码示例:
NSMutableString *str = [NSMutableString stringWithFormat:@"deepcopy"];NSArray *array = @[str];NSArray *arraycopy = [[NSArray alloc] initWithArray: array copyItems: YES];NSLog(@"%p - %p", array[0], arraycopy[0]);NSLog(@"%p - %p", array, arraycopy);
运行结果:
但是,当容器里面的元素仍然是容器的时候,我们使用这个方法,在里面的容器的里面的元素仍然是浅拷贝,里面的容器地址是深拷贝后的新地址
代码示例:
NSMutableString *str = [NSMutableString stringWithFormat:@"deepcopy"];NSArray *array = @[str, @[@"nested copy"]];NSArray *arraycopy = [[NSArray alloc] initWithArray: array copyItems: YES];NSLog(@"%p - %p", array, arraycopy);NSLog(@"%p - %p", array[0], arraycopy[0]);NSLog(@"%p - %p", array[1][0], arraycopy[1][0]);
运行结果:
显然,里面的容器的里面的元素仍然是浅拷贝,里面容器里的元素的地址仍然是原里面容器的元素的地址,这个时候,就要用到下面的递归深拷贝
递归深拷贝
在容器中嵌套容器时,需要递归的拷贝所有子容器及其内部元素,即要用到归档/解档的方法来进行递归深拷贝,这才是最彻底的深拷贝
代码示例:
NSArray *array01 = @[@[@"1", @"2"], @"3"];//进行安全归档NSData *data = [NSKeyedArchiver archivedDataWithRootObject: array01 requiringSecureCoding: YES error: nil];
//进行安全解档NSArray *array02 = [NSKeyedUnarchiver unarchivedObjectOfClass: [NSArray class] fromData: data error: nil];NSLog(@"%p - %p", array01[0][1], array02[0][1]);
运行结果:
总结
这次算是学习了当时没学到的部分属性关键字和深浅拷贝的更全面的内容,以后学到新的还会继续补充