属性关键字
在定义属性时,经常遇到属性关键字,例如定义UI控件时,经常使用nonatomic以及strong两个属性关键字,但没有了解过他们是干什么的,今天进行了浅层次的学习。
@property
当我们写下@property NSOject *foo时,编译器会自动创建实例变量_foo,帮我们声明
foo属性的setter,getter
方法,这个过程是在编译阶段
执行自动合成,这里生成的成员变量的名字会在属性名前加下划线,作为实例变量的名字。
一些常见的属性关键字及其作用
属性关键字大致上可分为三种,ARC环境下的关键字,MRC环境下的关键字,以及不分环境的关键字。
ARC环境:
strong,weak
MRC环境:
retain
不分环境:
copy,assign,nonatomic,atomic,readonly,readwrite,getter,setter,class,unsafe_unretained.
atomic
:原子性访问。
nonatomic
:非原子性访问,多线程并发访问会提高性能。
assign
:不会使引用计数加1,也就是直接赋值。
copy
:建立一个索引计数为1的对象,在赋值时使用传入值的一份拷贝。
strong
:ARC下可使用,使引用计数加1.
retain
:MRC下可以使用,使引用计数加1.
readwrite
:这个关键字说明属性被当成读写的,即拥有setter,getter方法,这个属性是默认属性。
readonly
:这个关键字说明属性只可读,也就是不能设置该属性,只能读取该属性。
weak
:建立一个索引计数为1的对象,在赋值时使用传入值的一份拷贝。
getter,setter
:可以重命名生成set get 方法名字,使用方法:
@property(nonatomic, copy, getter = string) NSString *firstString;
class
:可以使属性通过类名加点语法获取(不常见)。
unsafe_unretained
:与assign类似,但更加安全不会出现野指针的情况。
如何使用copy关键字
在定义NSString,NSArray等不可变NS类时,经常使用了copy关键字,这是因为他们存在可变类型NSMutableString,NSMutableArray等。
copy方法返回的都是不可变的对象,利用copy关键字可以使本对象的属性不受外界影响,不论传入的是可变对象还是不可变对象,本身持有的就是copy复制的不可变副本。
这里还涉及到了深拷贝与浅拷贝的问题
浅拷贝
:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数
深拷贝
:对对象内容的复制,开辟新的内存空间
由上可知可变对象的copy和mutableCopy都是深拷贝,不可变对象的copy是浅拷贝,mutableCopy是深拷贝,copy返回的都是不可变的对象。
所以可变的对象
不能使用copy关键字
,因为copy出的是不可变的对象,然而却把他当作可变对象来使用,调用可变对象的方法,很容易造成程序的崩溃。
自定义类使用copy关键字
若想让自定义类使用copy属性,那么就需要实现NSCopying 协议。
之前写的博客中有实现此协议的方法:OC读书笔记——对象复制。
strong和copy的区别
strong关键字的作用就是使引用计数加一并持有该对象,使用改关键字的对象在生命周期结束后才会被释放。
一般我们都使用copy来修饰NSString,那么strong可以修饰NSString,答案当然是可以的,那么他俩有什么区别呢。
这里举例了一段代码:
import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSString* firstString;
@property (nonatomic, copy) NSString* secondString;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSMutableString* textString = [[NSMutableString alloc] initWithString:@"str"];
self.firstString = textString;
self.secondString = textString;
NSLog(@"textSting:%@,firstString:%@,secondStirng:%@",textString,_firstString,_secondString);
NSLog(@"textSting:%p,firstString:%p,secondStirng:%p",textString,_firstString,_secondString);
}
[textString appendString:@"str"];
NSLog(@"textSting:%@,firstString:%@,secondStirng:%@",textString,_firstString,_secondString);
NSLog(@"textSting:%p,firstString:%p,secondStirng:%p",textString,_firstString,_secondString);
@end
这是打印结果:
可以看出在修改赋值的字符串后,使用strong修饰符的字符串内容改变了,而使用copy修饰符的字符串内容并无改变,这就是为什么一般使用copy而不是strong的原因,使用strong修饰,只是传递了指针,也就是浅拷贝,对原来数据进行修改,传递后的数据也会修改。
下面又举了一个例子:
#import "ViewController.h"
@interface ViewController ()
// strong 类型 NSArray
@property (nonatomic, strong) NSArray *strongArray;
// copy 类型 NSArray
@property (nonatomic, copy) NSArray *copyedArray;
@property (nonatomic, strong) NSString* firstString;
@property (nonatomic, copy) NSString* secondString;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *tmpArray = [NSArray arrayWithObjects:@"1",@"2", nil];
self.strongArray = tmpArray;
self.copyedArray = tmpArray;
NSLog(@"tmpArray: %@, %p", tmpArray, tmpArray);
NSLog(@"self.strongArray: %@, %p", self.strongArray, self.strongArray);
NSLog(@"self.copyedArray: %@, %p", self.copyedArray, self.copyedArray);
打印结果如下:
我们发现他们三个的地址居然是相同的
,仔细观察,第一个例子的来源对象属于可变
类型,第二个例子的来源对象是不可变
类型。
可以得出如下结论:
如果数据来源是可变对象
,那么使用strong和copy的地址是不同的,copy会进行深拷贝出一个新的地址的副本。
而当数据来源是不可变对象
,那么使用strong和copy的地址是想同的,都指向来源数组的内存地址。
如果确定来源是不可变类型,这种情况下用strong类型。因为copy修饰的不可变类型在进行set操作时,底层进行了这样的判断if ([str isMemberOfClass: [不可变NS class]]),如果来源是可变的,就进行一次深拷贝,如果是不可变的就和strong修饰一样,进行一次浅拷贝,
当项目庞大时,有成百上千个NSArray对象,多少会损耗app性能。
注意:如果来源对象是一个可变数组,尽管使用copy关键字,但是copy后数组内的指针还是指向原来数组元素指针指向的对象,如果该对象改变,那么copy的数组元素也会改变。