天天看点

深入理解@property的关键字:weak,strong,copy,assign,unsafe_unretained

关于属性这些关键字的文章,相信大家已经看过不少了,不过都是千篇一律的东西,并且其中有很多的错误。那么今天我们就来讨论讨论。

首先我们还是来介绍一下这些关键字的作用吧。

我认为:weak,strong,copy,assign,unsafe_unretained这些关键字其实就是这个属性的set语义。

weak:既不释放旧值,也不保留新值。当它指向的内容被释放的时候,它本身也会被置空。好处就是可以防止野指针访问。

weak的实现原理:系统通过一个CFMutableDictionary的hash表来管理weak指针。一个对象可能有多个weak指针指向,当这个对象被释放的时候,就会从hash表中查找所有指向这个对象的weak指针,并把它们都置为nil。

strong:释放旧值,并保留新值。它会对指向的内容进行引用计数+1的操作。

copy:释放旧值,并拷贝新值。它也会对指向的内容进行引用计数+1的操作。

assign:就是一个简单的赋值操作。它和weak很像,不会增加引用计数,但是当它指向的内容被释放的时候,它不会被置空,所以它还指向那个内容,所以容易造成野指针访问。一般用来修饰基本数据类型。当然指向对象也可以的。

unsafe_unretained:可以理解为MRC下的weak。和assign类似,同样不会自动置空,只能用来修饰对象类型。容易造成野指针访问。

首先声明一下,他对于这些关键字的解释是没有问题的,但是举的例子有问题。

po一下网址:http://www.cnblogs.com/langtianya/p/3691035.html

这是我随便百度出来的。

PS:想要有深入的理解,必须掌握好内存的相关知识。

首先,我们按照他说的,写个代码,为了方便,我把地址也打印出来:

@interface ViewController ()

@property (nonatomic, strong) NSString *string1;
@property (nonatomic, weak) NSString *string2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.string1 = @"hahaha";
    self.string2 = self.string1;
    NSLog(@"Str1 point to Address: %p", self.string1);
    NSLog(@"Str2 point to Address: %p", self.string2);

    self.string1 = nil;
    NSLog(@"String 1 = %@", self.string1);
    NSLog(@"String 2 = %@", self.string2);
    NSLog(@"Str1 point to Address: %p", self.string1);
    NSLog(@"Str2 point to Address: %p", self.string2);
}

@end
           
深入理解@property的关键字:weak,strong,copy,assign,unsafe_unretained

这是网站上说的,结果是空???你信么?是不是大家都觉得结果肯定是空?我运行一下。。。

深入理解@property的关键字:weak,strong,copy,assign,unsafe_unretained

嘿~有意思了吧~并不是空!!!而且这里你改成什么都是一样的,比如,改成如下的:

self.string1 = @"hahaha";
self.string2 = self.string1;
NSLog(@"Str1 point to Address: %p", self.string1);
NSLog(@"Str2 point to Address: %p", self.string2);

self.string1 = @"hehehe";
NSLog(@"String 1 = %@", self.string1);
NSLog(@"String 2 = %@", self.string2);
NSLog(@"Str1 point to Address: %p", self.string1);
NSLog(@"Str2 point to Address: %p", self.string2);
           

结果毫无疑问,还是String 2 = hahaha

为什么?学过MRC的同学肯定知道这是怎么回事了。其实很好解释,一开始,string1和string2都指向同一个地址,但之后,string1已经不再指向这个地址了,而是指向了其他地址。也就是说,一开始string1指向的那个地址对应的值并没有改变,而string2正是指向的这个地址。

举个例子,string1牵着一只狗(self.string1 = @”hahaha”),此时string2也牵着这只狗(self.string2 = self.string1),现在这只狗有两个绳子牵着。

情景1:突然,string1的绳子断了(self.string1 = nil),但是string2的绳子并没有断啊。

情景2:突然string1不喜欢这只狗了,去牵了另一只狗(self.string1 = @”hehehe”),但是此时string2还是牵着以前的那只。

这么说就很好理解了吧。关键就是这一句:self.string2 = self.string1

如果换成C语言的基本数据类型,可能会更好理解一些:

int a = ;
int b = a;
a = ;
printf("%d", b);
           

你们说,输出的b的结果是多少?毫无疑问是10啊!

所以说,上面的self.string2 = self.string1做的就是这样的事情。

到此,可能大家已经明白了。在这个属性中,无论你string2用的是weak/strong/copy/assign,结果都是一模一样的。因为被改变的不是string1指向的内容,而是string1本身的指向。

所以想要真正去测试那些属性的语义,必须有一个正确的方法:要被改变的是内容,而不是指向。

定义一个Person类:

@interface Person : NSObject <NSCopying>

//这里用strong或copy都可以的,原因同上
@property (nonatomic, copy) NSString *name;

@end
           

实现Copy方法。重写description方法,让它输出name的值

- (NSString *)description {
    return self.name;
}

- (id)copyWithZone:(NSZone *)zone {
    Person *newPer = [[self class] allocWithZone:zone];
    newPer.name = self.name;
    return newPer;
}
           

接下来我们开始测试了~

@interface ViewController ()

@property (nonatomic, copy/strong/weak/assign/unsafe_unretained) Person *per2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

        Person *per1 = [[Person alloc] init];
    per1.name = @"Tom";

    self.per2 = per1;

    //情景1
    per1.name = @"Jerry";
    //情景2
    per1.name = nil;
    //情景3
    per1 = nil;
    //情景4
    per1 = [[Person alloc] init];
    per1.name = @"NewName";

    NSLog(@"per1 - %@", per1);

    //PS:专门把per2的log写在外面,就是考虑到作用域的问题。也就是说,出了下面这个大括号,per1有可能会被释放。就看用什么关键字来修饰per2了
    [self logResult];
}

- (void)logResult {
    NSLog(@"per2 - %@", self.per2);
}

@end
           

上面的情景4在上文已经介绍过了,情景1和情景2都属于改变了指向的内容,情景3和情景4都是改变了自己本身的指向。

对于weak

情景1:两个都是 Jerry

情景2:两个都是 空

情景3:两个都是 空

情景4:per1为 NewName,per2为 空

对于strong

情景1:两个都是 Jerry

情景2:两个值都是 空

情景3:per1为 空,per2为 Tom

情景4:per1为 NewName,per2为 Tom

对于copy

为了实现copy,需要遵守NSCopying协议,实现-copyWithZone方法。

情景1:per1为 Jerry,per2为 Tom

情景2:per1为 空,per2为 Tom

情景3:per1为 空,per2为 Tom

情景4:per1为 NewName,per2为 Tom

对于assign

情景1:两个都是 Jerry

情景2:两个都是 空

情景3:per1为 空,per2无法输出,出现野指针错误。

情景4:per1为 NewName,per2无法输出,出现野指针错误。

对于unsafe_unretained

情景1:两个都是 Jerry

情景2:两个都是 空

情景3:per1为 空,per2无法输出,出现野指针错误。

情景4:per1为 NewName,per2无法输出,出现野指针错误。

继续阅读