iOS中主要的属性修饰符有下面几种:
读写权限:readonly, readwrite
安全策略:nonatomic,atomic (决定编译器生成的getter和setter是否为原子操作)
内存管理:copy / assign / retain,strong / weak / unsafe_unretained
方法名 :getter,setter
不常用:nonnull, null_resettable,nullable
按照MRC和ARC区分
MRC:copy / assign / retain / readwrite / readonly /nonatomic / atomic 等
ARC:copy / assign / strong / weak / readwrite / readonly /nonatomic / atomic 等
在声明 property 时,如果不指定关键字,编译器会为 property 生成默认的关键字
基本数据类型,默认关键字为:atomic,assign,readwrite
对象类型,默认关键字为 :atomic,strong,readwrite
下面介绍一下这些关键字
1、strong (默认)
强引用。用于OC对象,为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再把新值设置过去,属性引用对象的引用计数 +1。
2、weak
对对象的弱引用,不增加对象的引用计数,也不持有对象,当对象消失后指针自动指向nil,所以这里也就防止了野指针的存在。
1>delegate使用weak修饰:因为代理一般都是指向控制器,会造成循环引用,无法释放,造成内存泄露。比如tableview,代码中经常会有:
//控制器强引用self.view,self.view强引用tableView
[self.view addSubview:self.tableView];
//delegate 引用控制器,如果delegate用strong修饰会导致循环引用,无法释放,造成内存泄漏
self.tableView.delegate = self;
self.tableView.dataSource = self;
2>UI中控件经常使用weak的原因:
UI控件之所以可以使用弱指针,是因为控制器有强指针指向UIView
UIView 有强指针指向Subviews数组,数组中由强指针指向控件
一般来说,类 “内部”的属性设置为strong,类“外部”的属性设置为weak。说到底就是一个归属权的问题。使用weak通常为了解决循环引用的问题
3、assign
默认属性关键字。 assign用于非指针变量,一般用于基础类型和C数据类型,这些类型不是对象,统一由系统栈进行内存管理。
assign也能修饰OC对象,但是assign修饰的对象在被释放后,其指针依然存在,不会被置为nil——出现了野指针。当访问这个野指针时,指向了原地址,而原地址有两种情况:
第一种情况:原地址没有改变,代码运行通过,但很有可能有逻辑bug。
第二种情况:原地址已经改变,结果不可预测,多数crash,也有可能出现其他莫名错误。
但是用weak来修饰的话,对象释放的时候会把指针置为nil,从而避免了野指针的出现。
为什么基本数据类型就可以使用assign,这涉及到堆和栈的问题,基本数据类型会被分配到栈空间,而栈空间是由系统自动管理分配和释放的,就不会造成野指针的问题。
4、copy 拷贝
拷贝。返回一个不可变对象。源对象是不可变对象是浅拷贝,类似 strong;当源对象是可变对象时执行的是单层深拷贝。对自定义对象使用copy时,自定义对象需要遵守NSCopying协议并实现copyWithZone方法。。详细见前面文章iOS--拷贝相关题 -
使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值变化会无意间篡改不可变类型对象原来的值。
5、retain
适用于NSObject及其子类。release旧值,retain新值。
retain是指针复制(浅复制),引用计数加1,而不会导致内容被复制。
retain是MRC下的关键字,ARC下使用strong代替。
6、readwrite(默认)
读写属性。@synthesize自动生成setter和getter方法,可以通过点语法读取和设置属性的值。
7、readonly
只读属性。@synthesize只会自动合成getter方法。如果使用点语法为属性赋值,会编译错误:Assignment to readonly property。
8、getter=
指定获取属性的方法名
@property(nonatomic, getter=getName, copy)NSString *str;
self.str = @"getter=getName";
NSLog(@"%@",self.getName);
2020-07-10 14:39:30.160040+0800 iOS_property[91440:5164076] getter=getName
9、setter=
指定设置方法的方法名。
@property(nonatomic, setter=setName:, copy)NSString *str;
[self setName:@"setter=setName:"];
NSLog(@"%@",self.str);
2020-07-10 14:42:28.162017+0800 iOS_property[91536:5166626] setter=setName:
10、unsafe_unretained
用于修饰对象类型,声明一个弱引用,和用assign修饰对象类型一样,当所修饰的对象销毁时,该属性不会被置为nil。
11、atomic(默认)
原子的。提供多线程安全,在多线程环境下,原子操作是必要的,atomic会给对象加锁(对象的setter和getter函数里)假设多个线程访问同一属性,所有不同线程上的操作都将依次顺序执行。atomic读写线程安全,但不是绝对安全,atomic只是给getter和setter加了个锁,只能保证代码进入getter或者setter函数内部时是安全的,一旦出了getter和setter,atomic无法保障多线程安全了。还有如果修饰的是集合类型属性,对集合属性的读写是安全的,但集合内添加移除元素就不保证安全了。
因为iOS中使用同步锁的开销比较大,会影响性能,所以有特殊要求安全性高的才使用atomic,一般属性声明都是nonatomic。
12、nonatomic
非原子性,指出访问器不是原子操作。在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值,对属性赋值的时候不加锁,多线程并发访问会提高性能。但是如有多线程同时访问同一个非原子的属性,可能会出现无法预料的结果,如果没有使用多线程间的通讯编程,nonatomic是一个非常好的选择。
13、nonnull
对象不能为nil;当赋值为nil时编译器会给出警告。
NS_ASSUME_NONNULL_BEGIN //之间的属性都被认为是nonnull的NS_ASSUME_NONNULL_END
14、nullable
对象可以为nil;属性默认是nullable的。
————————————————————————————————————————
部分面试题
1、关于weak 与assign
weak:弱引用, ARC后出现,只可以修饰对象(delegate和block等引用类型),就是指针类型,不会改变引用对象的计数;不能修饰基本数据类型,否则会报错:Property with 'weak' attribute must be of object type。weak引用的对象释放后,指针会被自动置为 nil,不会出现野指针,也避免循环引用。
assign:通常用于修饰基本数据类型。如果修饰对象,不会改变引用对象的计数,引用的对象释放后指针不会自动被置空而产生野指针问题,此时向对象发消息会崩溃。
2、关于copy与strong
NSString、NSArray、NSDictionary常用copy,为什么不用strong?
见文章iOS--拷贝相关题 -
3、weak如何实现自动赋nil
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个哈希表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)的数组,因为一个对象可能被多个弱引用指针指向,所以这里Value是一个数组。
当这个对象引用计数为 0 时就会dealloc。假如 weak 指向的对象内存地址是address,那么就会以address为Key, 在这个 weak 表中搜索,找到以address为Key的指向这个对象的weak 指针的数组,遍历数组将weak指针设置为 nil。
weak实现原理可概括四步:
一、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
二、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
三、对象释放。
1. objc_release
2. 因为引用计数为0所以执行dealloc
3. _objc_rootDealloc
4. object_dispose
5. objc_destructInstance
6. objc_clear_deallocating
四、对象释放时,调用objc_clear_deallocating函数。
1、objc_clear_deallocating函数首先根据对象地址获取所有weak指针的数组;
2、然后遍历这个数组把其中的weak指针设为nil;
3、把这个记录从weak表中删除;
4、从引用计数表中清理废弃对象的地址作为键值的记录
https://opensource.apple.com/source/objc4/objc4-646/runtime/objc-weak.h