@proterty属性分类

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 = <TestObj:0x7f97a9c73740>;
内存:前:*_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 = <TestObj:0x7fdaf1f54300>;
内存:前:_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 = <TestObj:0x7fdaf1f54300>;
内存:前:*_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.namemyName指向的是同一片内存、所以修改一个值后两个值都变了)

那么使用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

  • atomicnonatomic用来决定编译器生成的gettersetter是否为原子操作。

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,原来那个对象就释放掉了。

推荐阅读更多精彩内容