我们都知道@property是用来声明属性的,可以保存类的状态或信息;而与其相关的内容,诸如copy、weak、深拷贝等,经常会在面试的过程中出现;
接下来深入下这些模糊&熟悉的内容,理理顺;
内容概要:
1.@property的本质;
2.自动合成 和 动态合成(动态绑定的应用);
3.原子性修饰符atomic 和 nonatomic;
4.atomic修饰的属性是线程安全的吗?
5.weak 和 strong;
6.__weak __strong __block;
7.assing 和 weak的使用;
7.1 基础数据类型使用weak修饰的问题;
7.2 对象类型使用assign修饰的问题;
7.3 堆 和 栈;
7.4 为什么基础数据类型可以使用assign修饰;
8.栈是线程安全的吗?
8.1 线程和进程的区别;
9.copy 和 strong的使用;
9.1 不可变对象使用strong的问题;
9.2 可变对象使用copy的问题;
10.copy和mutableCopy;
10.1深拷贝和浅拷贝;
10.2 自定义对象如何支持copy方法;
@property = instance ivar + setter + getter:
注意:
@synthesize的场景是,如果不手动实现setter和getter,那么编译器会自动为你加上,这是在编译期间;
我们都知道OC通过动态绑定,在运行时将消息及消息的接收者对应起来的(程序未运行时,两者是不会对应的);
OC使用的@dynamic指令,可以告诉编译器 与属性关联的方法会以动态的方式实现;所谓动态实现,实际就是开发者自己实现,然后在运行时进行关联,就是上图中我们所说的动态合成,而不使用编译器自动生成的setter和getter;
@property修饰符:
atomic非线程安全的解释说明:
atomic和nonatomic的区别在于,系统自动生成的getter和setter方法不一样;
如果我们实现自己的getter和setter,那么内存管理和原子性相关的@property修饰符仅仅是提示,写不写都一样;
atomic修饰的属性,系统生成的getter和setter会保证get/set操作的完整性,不受其他线程的影响;
一个属性的多个set/get在不同的线程是线程安全,但操作属性的其他方法不一定;
离开set/get方法之后的其他执行语句更是如此,也就是说atomic并不能保证所在线程的安全;
atomic让CPU能在别的线程来访这个属性之前,先执行完当前流程;
nonatomic的话,如果两个线程访问同一个属性,会出现无法预料的结果;
在如下示例的log中:
我们看到每次打印时getter获取的值都是有效的,也就是说本身get值,并没有受到其他线程的影响而取不出来;
但一定要注意,原子性只是保证了set和get方法的操作完整性;
对所在的线程中其他执行语句并没有约束力,所以不是线程安全的;
(假设存在这种约束力,那么线程a:——a1之后的log,应该是线程b:-b1-a1)
上述是atomic非线程安全的一种解释,还可以这样理解:
假设有一个atomic属性,线程1、2调用setter方法,线程3调用getter方法,在不同的线程中,这三个方法终将以某个顺序一次执行;
也就是说,一个线程在执行setter/getter方法时,其他线程只能等待;
但是,如果线程4中调用了release方法,就会有问题,因为release不受setter/getter方法的约束;
也就是说atomic修饰的属性,读(get)写(set)是安全的,但不是线程安全的;
因为别的线程还能进行读写之外的操作,这些操作都不会被约束,线程安全需要开发者自己保证;
弱引用和强引用;
weak修饰的属性,赋值时,不会对赋值的对象retain,引用计数不会+1,所引用的对象=nil时,该属性还会自动置为nil;
strong修饰的属性,赋值时,会对赋值的对象retain,引用计数会+1,strong是默认的缺省修饰符;
二者的根本区别是在set方法中,虽然来两者的set方法都是对旧对象release释放,对新对象retain保留,但
weak的set方法在retain之后会进行一次autorelease,而strong不会,最终效果就是strong引用计数会+1,weak则不会;
主要用于解决循环引用;
__weak 和 __strong关联的变量修饰符(引用类型):
在调用block的时候是采用copy方式,这是因为一般生成的block都是在栈上,通过copy之后的block会在堆上,相应的引用计数也会增加;
copy到堆上时,block的外部变量也会被copy,对象类型的引用变量的引用计数会+1,如果这个对象对block持有,block中又使用了(捕获)这个对象,就会造成循环引用,这会导致对象无法释放;
此时只需要在block外部重新定义一个采用__weak修饰的变量,这个变量指向object,然后在block中使用此变量即可;
虽然使用了__weak解决了循环引用的问题,但是这个变量也有可能随时被释放掉,为了能够使用该对象,可以在block内部一开始的地方定义一个__strong的变量,这个__strong变量指向外部定义的__weak变量;
使用方式:
这保证了整个block中对象一直被强引用而不被释放;
__weak typeof(self) wself = self;
>block{
__strong typeof(wself) sself = wself;
}
__block存储类型修饰符(值类型):
如果需要在block中修改外部的变量值,这个变量可以是静态变量、静态全局变量,全局变量;
原因是:全局变量 是存储在全局区,静态变量传递给Block的内存地址;
还有一种方式是对自动变量的:
自动变量的值,被copy进了Block,不带__block的自动变量只能在里边被访问,并不能改变值;
带__block的自动变量和静态变量就是直接地址访问,这么说不太严格:自动变量和静态变量还不一样,这里静态变量指的是静态局部变量,他确实是在block底层的结构体上存储了变量的指针,而使用__block修饰的自动变量,则是生成了一个新的结构体,保存了自动变量的地址和值,block结构体实际保存的是这个新的结构体指针,所以Block里可以直接改变便来能的值;
带有__block修饰符的变量会被捕获到Block内部持有;
我们介绍了使用assign修饰的属性,在对象释放之后,变成野指针,带来崩溃,那为什么基本数据类型可以使用assign修饰呢?
我们深入堆栈来看下:
使用assign修饰的基本数据类型之所以没有野指针,是因为:
基本数据类型是分配在栈上,栈空间的分配和回收是由系统管理的,也就不会产生野指针的问题了;
栈是线程安全的吗?
我们先来回顾一下进程和线程;
进程和线程的关系:
线程是进程的实体,是CPU调度和分派的基本单位;
一个进程可以有多个线程,线程本身只有很少的,运行时必要的资源,不分配系统资源;
线程与同一进程的其他线程共享进程资源;
一个进程中的所有线程共享该进程的地址空间,但是每个线程有自己独立的栈,堆则是进程独有的,为进程中其他线程共享;
所以栈 是 线程安全的:
栈是每个线程独有,保存线程的运行状态和局部变量;会在线程开始的时候分配;
堆是多个线程所共有的空间,操作系统在对进程进行初始化的时候,对堆进行分配;
@property修饰符的进一步说明:copy、string、mutableCopy
copy和strong:
@property (nonatomic , copy) NSString *sex;
@property (nonatomic , strong) NSMutableArray * books;
通常:
不可变对象属性修饰符使用copy;
可变对象属性修饰符使用strong;
可变对象和不可变对象:
不可变对象,如NSArray,NSString等;(改变变量的值,指针的地址会重新分配赋值)
可变对象,如NSMutableArray,NSMutableString等;(改变变量的值,指针的地址不变)
不可变对象使用strong的问题:
我们使用了strong修饰了strongStr属性,这是一个NSString类型,很明显我们需要的是一个不可变对象;
依照如下示例,我们会发现,虽然打印的两个变量的地址一致,但是地址在没有改变的情况下,不可变对象属性的值被篡改了;
可变对象使用copy的问题:
我们使用copy修饰了mutiStr属性,这是一个NSMutableString类型,很明显我们需要的是一个可变类型的对象;
依照如下示例,我们发现,程序报错,原因在于mutiStr属性实际是NSString类型,没有appendString方法;
我们来分析下copy修饰的可变对象属性赋值时到底发生了什么:
self.mutiStr = [NSMutableString stringWithString:tempStr];
<=>
NSMutableString tempString = [NSMutableString stringWithString: tempStr];
self.mutiStr = [tempString copy];
这里要注意,通过copy方法得到的对象是一个不可变的对象(使用mutableCopy方法返回的才是可变对象),自然没有可变对象的方法;
copy和mutableCopy:
二者的差异主要和深拷贝和浅拷贝有关;
深拷贝和浅拷贝:
浅拷贝:引用计数+1,并没有申请新的内存区域,只是另一个指针指向了该区域;
深拷贝:申请新的内存区域,与原内存区域中的内容是一样的,原内存区域的引用计数不变;
可变对象的copy和mutableCopy:
可变对象的copy和mutableCopy都是深拷贝,只不过方法返回的对象类型不同;
不可变对象的copy和mutableCopy:
不可变对象的copy是浅拷贝;
mutableCopy是深拷贝;
自定义对象如何支持copy方法:
自定义对象支持copy方法,只需要支持NSCopying协议,且实现copyWithZone方法;
对于当前对象的可变对象属性,需要使用mutableCopyWithZone操作;