属性关键字的类型
在iOS中属性关键字分为四种类型:
- 可访问性: readonly ,readwrite
- 原子性 : atomic ,nonatomic
- 内存管理 : retain/strong/copy, assign/unsafe_unretained,weak
- 方法命名:setter,getter
1.可访问性:
readonly:表示只读属性,只会生成getter方法 ,不会生成setter方法,无法通过 setter 方法进行修改。
readwrite:与 readonly 相对,表示读写属性,会生成getter和setter方法,可以通过 setter 方法修改属性值。
2.原子性:
atomic:提供了一定程度的线程安全性,确保对属性的读取和写入操作是原子的,即在同一时间只有一个线程可以访问属性。这是通过给属性的访问器方法(getter 和 setter)加锁来实现的。
nonatomic:属性的访问是非原子的,不提供线程安全保护。在多线程环境下,多个线程同时访问和修改属性可能会导致数据不一致的情况。nonatomic 通常具有更高的性能,因为不需要额外的加锁和同步操作。
atomic只针对属性的 getter/setter 方法进行加锁,所以安全只是针对getter/setter方法来说,并不是整个线程安全,因为一个属性并不只有 setter/getter 方法,例:(如果一个线程正在getter 或者 setter时,有另外一个线程同时对该属性进行release操作,如果release先完成,会造成crash)
3.内存管理:
weak
用于表示对对象的弱引用,不会增加对象的引用计数。当所引用的对象被释放时,弱引用会自动置为 nil。
在ARC环境下,为避免循环引用,往往会把delegate属性用weak修饰;在MRC下使用assign修饰。当一个对象不再有strong类型的指针指向它的时候,它就会被释放,即使还有weak型指针指向它,那么这些weak型指针也将被清除。
assign
经常用于非指针变量,用于基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id类型。用于对基本数据类型进行复制操作,不更改引用计数。
也可以用来修饰对象,但是,被assign修饰的对象在释放后,指针的地址还是存在的,也就是说指针并没有被置为nil,成为野指针。
之所以可以修饰基本数据类型,因为基本数据类型一般分配在栈上,栈的内存会由系统自动处理,不会造成野指针。MRC下的delegate往往assgin,此操作是为了deletage和self等自身产生循环引用。
weak 和 assign 的区别:
修饰的对象:weak修饰oc对象类型的数据,assign用来修饰是非指针变量。
引用计数:weak 和 assign 都不会增加引用计数。
释放:weak 修饰的对象释放后,指针地址自动设置为 nil,assign修饰的对象释放后指针地址依然存在,成为野指针。
修饰delegate 在MRC使用assign,在ARC使用weak。
strong
修饰一些OC对象类型的数据如:(NSNumber,NSString,NSArray、NSDate、NSDictionary、模型类等),strong是强引用,在ARC下等于retain,这一点区别于weak。
strong是我们通常所说的指针拷贝(浅拷贝),内存地址保持不变,只是产生了一个新的指针,新指针和引用对象的指针指向同一个内存地址,没有生成新的对象,多了一个指向该对象的指针。
由于使用的是一个内存地址,当该内存地址存储的内容发生变更的时候,会导致属性也跟着变更
copy
用于修饰OC对象类型的数据,在调用setter方法给成员变量赋值时,会将被赋值的对象生成一个副本,然后将该副本赋值给成员变量。
在MRC,用来修饰block,因为block需要从栈区copy到堆区,在ARC,系统自动给我们做了这个操作,所一现在使用strong或者copy来修饰block都是可以的。
copy和strong都是属于强引用,都会让属性的引用计数加一,但是copy和strong不同点在于,它所修饰的属性当引用一个属性值时,是内存拷贝(深拷贝),就是在引用是,会生成一个新的内存地址和指针地址,和引用对象完全没有相同点,因此它不会因为引用属性的变更而改变。
copy与strong的区别(深拷贝 浅拷贝):
copy:内存拷贝-深拷贝,内存地址不同,指针地址也不同。
storng: 指针拷贝-浅拷贝,内存地址不变,指针地址不同。
声明两个copy属性,两个strong属性,分别为可变和不可变类型:
@property(nonatomic,strong)NSString * Strstrong;
@property(nonatomic,copy)NSString * Strcopy;
@property(nonatomic,copy)NSMutableString * MutableStrcopy;
@property(nonatomic,strong)NSMutableString * MutableStrstrong;`
用不可变对象对属性进行赋值:
```
NSString * OriginalStr = @"我已经开始测试了";
//对 不可变对象赋值 无论是 strong 还是 copy 都是原地址不变,生成一个新指针指向对象(浅拷贝)
self.Strcopy = OriginalStr;
self.Strstrong = OriginalStr;
self.MutableStrcopy = OriginalStr;
self.MutableStrstrong = OriginalStr;
NSLog(@"rangle=>%@\n normal:copy=>%@=====strong=>%@\nMutable:copy=>%@=====strong=>%@",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",OriginalStr,_Strcopy,_Strstrong,_MutableStrcopy,_MutableStrstrong);
NSLog(@"rangle=>%p\n normal:copy=>%p=====strong=>%p\nMutable:copy=>%p=====strong=>%p",&OriginalStr,&_Strcopy,&_Strstrong,&_MutableStrcopy,&_MutableStrstrong);
```
strong修饰的对象和copy修饰的对象都是在引用一个对象的时候,内存地址是一样的,只有指针地址不同。
所以对于不可变对象进行赋值,使用strong和copy关键字都是进行的浅拷贝,即对象内存地址不变,指针地址改变
用可变对象对属性进行赋值:
```
NSMutableString * OriginalMutableStr = [NSMutableString stringWithFormat:@"我已经开始测试了"];
self.Strcopy = OriginalMutableStr;
self.Strstrong = OriginalMutableStr;
self.MutableStrcopy = OriginalMutableStr;
self.MutableStrstrong = OriginalMutableStr;
```
strong修饰的属性内存地址依然没有改变,但是copy修饰的属性内存值产生了变化
对可变对象赋值 strong 是原地址不变,引用计数+1(浅拷贝)。 copy是生成一个新的地址和对象,生成一个新指针指向新的内存地址(深拷贝)
此时修改一下OriginalMutableStr的值,看看结果:
```
[OriginalMutableStr appendFormat:@"改变了"];
```
当改变原来的值后strong修饰的属性的内容也跟着改变了,而copy修饰的属性的内容没有发生改变。
由于OriginalMutableStr是可变类型,是在原有内存地址上进行修改,无论是指针地址和内存地址都没有被改变,只是当前内存地址所存放的数据进行改变。
由于 strong 修饰的属性虽然指针地址不同,但是指针是指向原内存地址的,所以会跟着 OriginalMutableStr 的改变而改变。
copy修饰的类型不仅指针地址不同,而且指向的内存地址也和OriginalMutableStr 不一样,所以不会跟着 OriginalMutableStr 的改变而改变。
使用self.Strcopy 和 _Strcopy 来赋值也是两个不一样的结果,因为后者没有调用 set 方法,而 copy 和 strong 之所以会产生差别就是因为在 set 方法中,copy修饰的属性: 调用了 _Strcopy = [Strcopy copy] 方法。
多种copy模式:copy 和 mutableCopy 对容器对象 进行操作
在对容器对象(NSArray)进行copy操作时,分为多种:
- copy:仅仅进行了指针拷贝
- mutableCopy:进行内容拷贝这里的单层指的是完成了NSArray对象的深copy,而未对其容器内对象进行处理使用(NSArray对象的内存地址不同,但是内部元素的内存地址不变)
[array copy];
[array mutableCopy];
- 双层深拷贝:这里的双层指的是完成了NSArray对象和NSArray容器内对象的深copy(为什么不是完全,是因为无法处理NSArray中还有一个NSArray这种情况)使用:
[[NSArray alloc] initWithArray:arr copyItems:YES]
-
完全深拷贝:完美的解决NSArray嵌套NSArray这种情形,可以使用归档、解档的方式可以使用:
[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:testArr]];
问题总结
- NSMutableArray用copy修饰会出现什么问题?
出现调用可变方法不可控问题,会导致程序崩溃。对于可变对象使用copy关键字会进行深拷贝,返回一个不可变的对象,对不可变的对象调用可变方法就会crash
2.copy关键字影响了对象的可变和不可变属性吗?
- 可变对象(mutable)copy和mutableCopy都是深拷贝
- 不可变对象(immutable)的copy是浅拷贝,mutableCopy是深拷贝