@property修饰符
- atomic nonatomic
- readwrite readonly
- retain
- assign
- copy
- strong
- weak
- unsafe_unretained
- autoreleasing
- setter getter
atomic nonatomic
atomic
(默认参数):原子性,性能低(一般开发OC中的APP不推荐使用,做金融等要求高安全的时候使用)
(原子性操作),会被加锁,就是一个操作执行过程不能被中断,要不就执行完,要不就不执行(一个操作不可以在中途被CPU暂停然后调度)。如果一个操作是原子性的,那么在多线程环境下,就不会出现变量被修改等奇怪的问题(保证数据同步)。原子操作就是不可再分的操作,在多线程程序中原子操作是一个非常重要的概念,它常常用来实现一些同步机制,同时也是一些常见的多线程bug的源头。
nonatomic
:非原子性,性能高(推荐使用,性能高)
(非原子性操作)操作是直接从内存中取数值(不考虑其是否被占用)。在多线程环境下可能提高性能,但无法保证数据同步。
readwrite readonly
readwrite
是编译器的默认选项,便是自动生成getter
和setter
,如果需要getter
和setter
不写即可。
readonly
表示只会合成getter
而不合成setter
。
assign weak unsafe_unretained
assign
表示对属性只进行简单的赋值操作,不更改所赋值的引用计数,也不改变旧的引用计数,常用于标量类型,如NSInteger
NSUInteger
CGFloat
NSTimerInterval
等。
assign
也可以修饰对象如NSString
等类型对象,上面说过使用assign
修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil
,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign
修饰的类型一定要为标量类型。
使用weak
修饰的时候同样不会增加所赋新值的引用计数,也不会减少旧值的引用计数,但当该值被销毁时,weak
修饰的属性会被自动赋值为nil
,这样就可以避免野指针错误。
使用unsafe_unretained
修饰时效果与assign
相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil
可能会发生野指针错误。unsafe_unretained
与assign
的区别在于,unsafe_unretained
只能修饰对象,不能修饰标量类型,而assign
两者均可修饰。
strong weak
strong
表示属性对所赋的值持有强引用表示一种“拥有关系”(owing relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong
。
weak
表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),对新值不会增加引用计数,也不会减少旧值的引用计数。所赋的值在引用计数为0被销毁后,weak
修饰的属性会被自动置为nil
能够有效防止野指针错误。
weak
常用在修饰delegate
等防止循环引用的场景。
copy
copy
修饰的属性会在内存里拷贝一份对象,两个指针指向不同的内存地址。
一般用来修饰有对应可变类型子类的对象。
如:NSString/NSMutableString
NSArray/NSMutableArray
NSDictionary/NSMutableDictionary
等。
为确保这些不可变对象因为可变子类对象影响,需要copy
一份备份,如果不使用copy
修饰,使用strong
或者assign
等修饰则会因为多态导致属性值被修改。
这里的copy
还牵扯到NSCopying
和NSMutableCopying
协议。
copy
还被用来修饰block
,在ARC环境下编译器默认会用copy
修饰,一般情况下在block
需要捕获外界数据时该block
就会被分配在堆区,但在MRC环境下由于手动管理引用计数,block
一般被分配在栈区,需要copy
到堆区来防止野指针错误。有一个栗子:
@interface Person : NSObject
//使用copy修饰NSMutableString
@property (nonatomic, copy) NSMutableString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
//将可变字符串赋值给p.name
p.name = s;
//输出的地址不一致,内容一致
NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
//修改p.name,此时抛出异常
[p.name appendString:@" is a good guy."];
}
return 0;
}
上面的栗子使用copy
修饰可变对象,在进行赋值的时候会通过copy
方法获取一个不可变对象,因此p.name
的地址和s
的地址不同,而p.name
运行时类型为NSString
,调用appendString:
方法会抛出异常。
所以,针对不可变对象使用copy
修饰,针对可变对象使用strong
修饰。
retain
在ARC环境下使用较少,在MRC下使用效果与strong
一致。
copy题外话
有时候我们需要copy
一个对象,或是mutableCopy
一个对象,这时需要遵守NSCopying
和NSMutableCopying
协议,来实现copyWithZone:
和mutableCopyWithZone:
两个方法,而不是重写copy
和mutableCopy
两个方法。
Foundation框架中的很多数据类型已经帮我们实现了上述两个方法,因此我们可以使用copy
方法和mutableCopy
方法来复制一个对象,两者的区别在于copy
的返回值仍为不可变对象,mutableCopy
的返回值为可变对象。
type | copy | mutableCopy |
---|---|---|
NS* | 浅拷贝,只拷贝指针,地址相同 | 单层深拷贝,拷贝内容,地址不同 |
NSMutable* | 单层深拷贝,拷贝内容,地址不同 | 单层深拷贝,拷贝内容,地址不同 |
对于不可变类型,使用copy
方法时是浅拷贝,只拷贝指针,因为内容是不会变化的。使用mutableCopy
时由于返回可变对象因此需要一份拷贝,供其他对象使用。对于可变类型,不管是copy
还是mutableCopy
均会进行深拷贝,所指向指针不同。
前文介绍copy
修饰符的时候讲过,在修饰NSString
这样的不可变对象的时候使用copy
修饰,但其实当给对象赋一个NSString
时仍旧只复制了指针而不是拷贝内容,原因同上。
与@property相关的问题
1. @property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的?
@property本质是什么?
@property = ivar + getter + setter
下面解释下:
"属性"(property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。
而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。
正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:
编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。
所以你也可以这么说:
@property = getter + setter;
例如下面这个类:
@interface Person : NSObject
@property NSString *firstNamel;
@property NSString *lastNamel;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
ivar、getter、setter是如何生成并添加到这个类中的?
“自动合成”(autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为_firstName
与 _lastName
。也可以在类的实现代码里通过@synthesize
语法来指定实例变量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
属性是怎么生成的?增加一个属性,底层大致生成五个东西:
-
OBJC_IVAR_$类名$属性名
:该属性的“偏移量“(offset),这个偏移量是”硬编码“(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。 -
getter
和setter
方法对应的实现函数 -
ivar_list
:成员变量列表 -
method_list
:方法列表 -
prop_list
:属性列表
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter和getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量的开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了强转。
2. @property中有哪些属性关键字?(或@property后面可以有哪些修饰符?)
属性可以拥有的特质分为四类:
1、原子性 --- nonatomic
特质
2、读写权限 --- readwrite
readonly
3、内存管理语义 --- assign
strong
weak
unsafe_unretained
copy
4、方法名 --- getter=<name>
setter=<name>
getter=<name>
的样子:
@property (nonatomic, getter=isOn) BOOL on;
(setter=<name>
这种不常用,也不推荐使用)
5、不常用的:nonnull
,null_resettable
,nullable
3. 什么情况下使用weak关键字,相比assign有什么不同?
什么情况下使用weak
关键字?
- 在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用
weak
来解决,比如:delegate
代理属性 - 自身已经对它进行一次强引用,没必要再强引用一次,此时也会使用
weak
,自定义IBOutlet
控件属性一般也使用weak
;当然,也可以使用strong
。
不同点:
-
weak
此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign
类似,然而在属性所指的对象遭到销毁时,属性值也会清空(nil out)。
而assign
的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或
NSlnteger 等)的简单赋值操作。属性所指的对象销毁时,编译器不会将该属性置为nil
,指针仍 旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃, - assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
4. 怎么用copy关键字?
-
NSString/NSArray/NSDictiony
等等经常使用copy
关键字,是因为它们有对应的可变类型:NSMutableString/NSMutableArray/NSMutableDictionary
; -
block
也经常使用copy
关键字
block
使用copy
是从MRC遗留下来的“传统”,在MRC中,方法内部的block
是在栈区的,使用copy
可以把它放到堆区。在ARC中写不写都行:对于block
使用copy
还是strong
效果是一样的,但写上copy
也无伤大雅,还能时刻提醒我们:编译器自动对block
进行了copy
操作。如果不写copy
,该类的调用者有可能会忘记或者根本不知道“编译器会自动对block
进行了copy
操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。
下面做解释:
copy
此特质所表达的所属关系与strong
类似。然而设置方法并不保留新值,而是将其“拷贝”(copy)。
当属性类型为NSString
时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString
类的实例。这个类是NSString
的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这是就要拷贝一份“不可变”(immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的“(mutable),就应该在设置新值时拷贝一份。
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
5. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
- 因为父类指针可以指向子类对象,使用
copy
的目的是为了让本对象的属性不受外界影响,使用copy
无论给我传入是一个可变对象还是不可变对象,我本身持有的就是一个不可变的副本。 - 如果我们使用是
strong
,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
copy
此特质所表达的所属关系与strong
类似。然而设置方法并不保留新值,而是将其”拷贝“(copy)。
copy
此特质所表达的所属关系与strong
类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。
当属性类型为NSString
时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString
类的实例。这个类是NSString
的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
举例说明:
定义一个以strong修饰的array:
@property (nonatomic, readwrite, strong) NSArray *marray;
然后进行下面的操作:
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
NSArray *array = @[@1, @2, @3, @4];
self.marray = mutableArray;
[mutableArray removeAllObjects];
NSLog(@"%@", self.marray);
[mutableArray addObjectsFromArray:array];
self.marray = [mutableArray copy];
[mutableArray removeAllObjects];
NSLog(@"%@", self.marray);
打印结果如下所示:
2018-06-15 15:35:43.292506+0800 getIP[49185:2440314] (
)
2018-06-15 15:35:43.292805+0800 getIP[49185:2440314] (
1,
2,
3,
4
)
为了理解这种做法,首先要知道,两种情况:
- 对非集合类对象的
copy
与mutableCopy
操作 - 对集合类对象的
copy
与mutableCopy
操作
1. 对非集合类对象的copy与mutableCopy操作
在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时时内容复制;
对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示为:
- [immutableObject copy] //浅复制
- [immutableObject mutableCopy]//深复制
- [mutableObject copy] //深复制
- [mutableObject mutableCopy]//深复制
比如以下代码:
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];
查看内存,会发现string
,stringCopy
内存地址都不一样,说明此时都是做了内容拷贝、深拷贝。即使你进行如下操作:
[string appendString:@"origion!"];
stringCopy
的值也不会因此改变,但是如果不使用copy
,stringCopy
的值就会被改变。
集合类对象以此类推。
所以,
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
2. 对集合类对象的copy与mutableCopy操作
集合类对象是指NSArray
、NSDictory
、NSSet
...之类的对象。下面先看集合类immutable
对象使用copy
和mutableCopy
的一个例子:
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:
在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制
这个代码结论与非集合类的非常相似。
总结
关于@property的问题,问法比较多,但是万变不离其宗,搞清楚底层原理,迎刃而解!