@proterty属性分类

2019-05-16  本文已影响0人  齊同学

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 

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了,所以该内存地址不会被回收

拓展

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

atomic

{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

在定义 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_;
}
//@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];
    }
}
上一篇 下一篇

猜你喜欢

热点阅读