Strong
强引用,对象的引用计数器值+1
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, strong) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:s2 = ;
内存:前:*_s1: 0x7f97a9c73740; *_s2: 0x7f97a9c73740
后:*_s1: 0x0; *_s2: 0x7f97a9c73740
可见,s1指向的地址中的内容已经不存在了,但是因为引用计数+1了所以该块内存不会被释放,可以继续访问
Assign
弱引用,对象的引用计数器值不变,用于基础类型(基础类型copy,基础类型没有引用计数的概念)
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, assign) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:crash 但是打印出了s2 = 0x7fb668419500 (这里有时候会crash,有时候不会,当这块地址被回收了,就会crash。因为s1和s2指向同一块地址,当s1被释放了,地址就极可能会被回收。)
内存:前:*_s1: 0x7fb668419500; *_s2: 0x7fb668419500
后:*_s1: 0x0; *_s2: 0x7fb668419500
可见,s1指向的地址已经被回收,所以s2找不到地址。
拓展
assign 简单赋值,不更改索引计数(Reference Counting)。
assign 说明设置器直接进行赋值,这也是默认值。
在使用垃圾收集的应用程序中,如果你要一个属性使用assign,且这个类符合NSCopying协 议,你就要明确指出这个标记,而不是简单地使用默认值,否则的话,你将得到一个编译警告。这再次向编译器说明你确实需要赋值,即使它是可拷贝的。
如果想在dellac中调用delegate的某些函数时候,如果是weak会找不到对象,因为被置空了。所以用assign
- 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char)等,另外还有id。如:
- @property (nonatomic, assign) int number;
- @property (nonatomic, assign) id className;//id必须用assign
- 反正记住:前面不需要加 “*” 的就用assign吧
Weak
弱引用,如果持有对象被释放,该对象也自动释放
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, weak) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:s2 = nil;
内存:前:*_s1: 0x7f9689554460; *_s2: 0x7f9689554460
后:*_s1: 0x0; *_s2: 0x0
可见,s1的内存地址被回收,s2的指针也变成nil,不会再指向该地址
Copy
拷贝一份、建立一个索引计数为1的对象,然后释放旧对象
//对象要实现NSCopy协议
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, copy) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:s2 = ;
内存:前:_s1: 0x7fdaf1f543e0; *_s2: 0x7fdaf1f54300
后:*_s1: 0x0; *_s2: 0x7fdaf1f54300
可见,s1、s2指针指向的地址是不同的 因为copy了一份,内容相同,不是原来的地址了,所以s1= nil,不影响s2
Retain
释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, retain) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:s2 = ;
内存:前:*_s1: 0x7ffb5875c1b0; *_s2: 0x7ffb5875c1b0
后:*_s1: 0x0; *_s2: 0x7ffb5875c1b0
可见,s1、s2指针指向的地址是相同的,当s1= nil,不影响s2。因为引用+1了,所以该内存地址不会被回收
对其他NSObject和其子类
xcode 4.2不支持ARC,所以会频繁使用retain来修饰,用完释放掉,而xcode4.3支持ARC,可以使用retian,不需要手动释放内存,系统会自动为你完成,如果你在xcode4.3上面开发,retian和strong都是一样的,没区别
拓展
retain是指针拷贝,copy是内容拷贝。在拷贝之前,都会释放旧的对象
unsafe_unretained
和assign类似,但是它适用于对象类型,当目标被摧毁时,属性值不会自动清空(unsafe)。这是和weak的区别
@property (nonatomic, strong) TestObj *s1;
@property (nonatomic, unsafe_unretained) TestObj *s2;
self.s1 = [[TestObj allow] init];
self.s2 = self.s1;
self.s1 = nil;
NSLog(@"s2 = %@",self.s2);
结果:crash;
内存:前:*_s1: 0x7fd5dbfa42f0; *_s2: 0x7fd5dbfa42f0
后:*_s1: 0x0; *_s2: 0x7fd5dbfa42f0
可见,s1、s2指针指向的地址是相同的,s1=nil,让该块地址被回收,当s2指向这个地址时,就会找不到
拓展
weak指针的前身,现在已被weak取代
与weak的最大区别是,unsafe_unretained所指向的对象在释放掉后,unsafe_unretained不会"归零"(weak指针会自动设置为nil),可能指向"僵尸"对象
NSString 为何要用copy、而不是strong
首先我们来看看使用strong
会出现什么情况:
.h
@property (nonatomic,strong)NSString *name;
.m
NSMutableString * myName = [NSMutableString stringWithFormat:@"拓跋"];
self.name = myName;
NSLog(@"使用strong第一次得到的名字:%@",self.name);
[myName appendString:@"野"];
NSLog(@"使用strong第一次得到的名字:%@",self.name);
打印结果:
2017-05-06 08:09:06.730 Quartz2DTest[11074:8265411] 使用strong第一次得到的名字:拓跋
2017-05-06 08:09:06.730 Quartz2DTest[11074:8265411] 使用strong第一次得到的名字:拓跋野
结论
通过上面的例子我们可以看出,我们没有直接修改self.name
的情况下self.name
却被修改了,我们的初衷只是想修改myName
,self.name却被修改了、而这就是我们使用strong
所不想看到的、它会破坏程序的封装性(使用strong
后、self.name
和myName
指向的是同一片内存、所以修改一个值后两个值都变了)
那么使用copy
又会得到什么结果呢?下面是使用copy
的例子:
.h
@property (nonatomic, copy) NSString *name;
.m
NSMutableString * myName = [NSMutableString stringWithFormat:@"拓跋"];
self.name = myName;
NSLog(@"使用strong第一次得到的名字:%@",self.name);
[myName appendString:@"野"];
NSLog(@"使用strong第一次得到的名字:%@",self.name);
打印结果:
2017-05-06 08:18:15.730 Quartz2DTest[11134:8279219] 使用strong第一次得到的名字:拓跋
2017-05-06 08:18:15.730 Quartz2DTest[11134:8279219] 使用strong第一次得到的名字:拓跋
结论
myName
通过copy得到了一个新的对象赋值给self.name
、这样我们再修改myName
就跟self.name
没什么关系了、只有对self.name
直接进行赋值才能改变它的值、这样就保证了程序的封装性。
atomic和nonatomic
-
atomic
和nonatomic
用来决定编译器生成的getter
和setter
是否为原子操作。
atomic
- 设置成员变量的
@property
属性时,默认为atomic
,提供读写安全。 - 在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:
- 会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程
- 速度不快,因为要保证操作整体完成
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全的,而atomic的操作是原子性的,但是并不意味着它是线程安全的,它会增加正确的几率,能够更好的避免线程的错误,但是它仍然是线程不安全的。
当使用nonatomic的时候,属性的setter,getter操作是非原子性的,所以当多个线程同时对某一属性读和写操作时,属性的最终结果是不能预测的。
当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。
也就是要注意:atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。如下列所示:
比如:@property(atomic,strong)NSMutableArray *arr;
如果一个线程循环的读数据,一个线程循环写数据,那么肯定会产生内存问题,因为这和setter、getter没有关系。如使用[self.arr objectAtIndex:index]就不是线程安全的。好的解决方案就是加锁。
据说,atomic要比nonatomic慢大约20倍。一般如果条件允许,我们可以让服务器来进行加锁操作。
nonatomic
- 禁止多线程,变量保护,提高性能。
- atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。
- 指出访问器不是原子操作,而默认地,访问器是原子操作。这也就是说,在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。
在定义 property 的时候,atomic 和 nonatomic 有何区别?
@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;
这仨有什么不同?
- 用背后的代码来解释
//@property(nonatomic, retain) UITextField *userName;
//系统生成的代码如下:
- (UITextField *) userName {
return userName;
}
- (void) setUserName:(UITextField *)userName_ {
[userName_ retain];
[userName release];
userName = userName_;
}
- 而 atomic 版本的要复杂一些:
//@property(retain) UITextField *userName;
//系统生成的代码如下:
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [[userName retain] autorelease];
}
return retval;
}
- (void) setUserName:(UITextField *)userName_ {
@synchronized(self) {
[userName release];
userName = [userName_ retain];
}
}
- 简单来说,就是
atomic
会加一个锁来保障线程安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调setter
,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。