strong和weak引用的讲解


由于这几天一直在学习ViewController之间的数据传输方法,学着学着就有了疑问:为什么在前向传输时(forward)可以使用属性传值,而后向传输(backward)时不能再使用,为了弄清楚这个问题,搜了很多文章,大部分都是在讲传输方法的使用,没有找到原因,但是根据蛛丝马迹找到了strong和weak这样的关键字,由于对这样的关键词还不了解,所以又专门来了解它们来了,看了下面这篇文章算是有了一个初步的认识,本来我想转化为自己的语言再描述一遍,但发现没有原文写的具体,所以就只好全文复制过来了,以防原网站无法访问,然后再贴上原链接供详细的了解。当然了,还有其他的参考链接就都贴在这里吧:

这篇文章也解释的很清楚,并且应该算是下面这篇的补充: http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html


参考链接:http://www.informit.com/articles/article.aspx?p=1856389&seqNum=5

Strong and Weak References

So far, we’ve said that anytime a pointer variable stores the address of an object, that object has an owner and will stay alive. This is known as a strong reference. However, a variable can optionally not take ownership of an object it points to. A variable that does not take ownership of an object is known as a weak reference.

A weak reference is useful for an unusual situation called a retain cycle. A retain cycle occurs when two or more objects have strong references to each other. This is bad news. When two objects own each other, they will never be destroyed by ARC. Even if every other object in the application releases ownership of these objects, these objects (and any objects that they own) will continue to exist by virtue of those two strong references.

Thus, a retain cycle is a memory leak that ARC needs your help to fix. You fix it by making one of the references weak. Let’s introduce a retain cycle in RandomPossessions to see how this works. First, we’ll give BNRItem instances the ability to hold another BNRItem (so we can represent things like backpacks and purses). In addition, a BNRItem will know which BNRItemholds it. In BNRItem.h, add two instance variables and accessors

@interface BNRItem : NSObject
{
    NSString *itemName;
    NSString *serialNumber;
    int valueInDollars;
    NSDate *dateCreated;
    BNRItem *containedItem;
    BNRItem *container;
}

+ (id)randomItem;

- (id)initWithItemName:(NSString *)name
        valueInDollars:(int)value
          serialNumber:(NSString *)sNumber;

- (void)setContainedItem:(BNRItem *)i;
- (BNRItem *)containedItem;

- (void)setContainer:(BNRItem *)i;
- (BNRItem *)container;

Implement the accessors in BNRItem.m.

- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;

    // When given an item to contain, the contained
    // item will be given a pointer to its container
    [i setContainer:self];
}

- (BNRItem *)containedItem
{
    return containedItem;
}

- (void)setContainer:(BNRItem *)i
{
    container = i;
}

- (BNRItem *)container
{
    return container;
}

In main.m, remove the code that populated the array with random items. Then create two new items, add them to the array, and make them point at each other.

#import <Foundation/Foundation.h>
#import "BNRItem.h"

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSMutableArray *items = [[NSMutableArray alloc] init];

        for (int i = 0; i < 10; i++) {
            BNRItem *p = [BNRItem randomItem];
            [items addObject:p];
        }

        for (BNRItem *item in items)
            NSLog(@"%@", item);

        BNRItem *backpack = [[BNRItem alloc] init];
        [backpack setItemName:@"Backpack"];
        [items addObject:backpack];

        BNRItem *calculator = [[BNRItem alloc] init];
        [calculator setItemName:@"Calculator"];
        [items addObject:calculator];

        [backpack setContainedItem:calculator];

        NSLog(@"Setting items to nil...");
        items = nil;
    }
    return 0;
}

Here’s what the application looks like now:

Figure 3.7

Figure 3.7 RandomPossessions with retain cycle

Per our understanding of memory management so far, both BNRItems should be destroyed along with their instance variables when items is set to nil. Build and run the application. Notice that the console does not report that these objects have been destroyed.

This is a retain cycle: the backpack and the calculator have strong references to one another, so there is no way to destroy these objects. Figure 3.8 shows the objects in the application that are still taking up memory once items has been set to nil.

Figure 3.8

Figure 3.8 A retain cycle!

The two BNRItems cannot be accessed by any other part of the application (in this case, themain function), yet they still exist in their own little world doing nothing useful. Moreover, because they cannot be destroyed, neither can the other objects that their instance variables point to.

To fix this problem, one of the pointers between the BNRItems needs to be a weak reference. To decide which one should be weak, think of the objects in the cycle as being in a parent-child relationship. In this relationship, the parent can own its child, but a child should never own its parent. In our retain cycle, the backpack is the parent, and the calculator is the child. Thus, the backpack can keep its strong reference to the calculator (containedItem), but the calculator’s reference to the backpack (container) should be weak.

To declare a variable as a weak reference, we use the __weak attribute. In BNRItem.h, change thecontainer instance variable to be a weak reference.

__weak BNRItem *container;

Build and run the application again. This time, the objects are destroyed properly.

Every retain cycle can be broken down into a parent-child relationship. A parent typically keeps a strong reference to its child, so if a child needs a pointer to its parent, that pointer must be a weak reference to avoid a retain cycle.

A child holding a strong reference to its parent’s parent also causes a retain cycle. So the same rule applies in this situation: if a child needs a pointer to its parent’s parent (or its parent’s parent’s parent, etc.), then that pointer must be a weak reference.

It’s good to understand and look out for retain cycles, but keep in mind that they are quite rare. Also, Xcode has a Leaks tool to help you find them. We’ll see how to use this tool in Chapter 21.

An interesting property of weak references is that they know when the object they reference is destroyed. Thus, if the backpack is destroyed, the calculator automatically sets its containerinstance variable to nil. In main.m, make the following changes to see this happen.

NSMutableArray *items = [[NSMutableArray alloc] init];

BNRItem *backpack = [[BNRItem alloc] init];
[backpack setItemName:@"Backpack"];
[items addObject:backpack];

BNRItem *calculator = [[BNRItem alloc] init];
[calculator setItemName:@"Calculator"];
[items addObject:calculator];

[backpack setContainedItem:calculator];

NSLog(@"Setting items to nil...");
items = nil;

backpack = nil;

NSLog(@"Container: %@", [calculator container]);

calculator = nil;

Build and run the application. Notice that after the backpack is destroyed, the calculator reports that it has no container without any additional work on our part.

A variable can also be declared using the __unsafe_unretained attribute. Like a weak reference, an unsafe unretained reference does not take ownership of the object it points to. Unlike a weak reference, an unsafe unretained reference is not automatically set to nil when the object it points to is destroyed. This makes unsafe unretained variables, well, unsafe. To see an example, change container to be unsafe unretained in BNRItem.h.

__unsafe_unretained BNRItem *container;

Build and run the application. It will most likely crash. The reason? When the calculator was asked for its container within the NSLog function call, it obligingly returned its value – the address in memory where the non-existent backpack used to live. Sending a message to a non-existent object resulted in a crash. Oops.

As a novice iOS programmer, you won’t use __unsafe_unretained. As an experienced programmer, you probably won’t use it, either. It exists primarily for backwards compatibility: applications prior to iOS 5 could not use weak references, so to have similar behavior, they must use __unsafe_unretained.

Be safe. Change this variable back to __weak.

__weak BNRItem *container;

Here’s the current diagram of RandomPossessions. Notice that the arrow representing thecontainer pointer variable is now a dotted line. A dotted line denotes a weak (or unsafe unretained reference). Strong references are always solid lines.

Figure 3.9

Figure 3.9 RandomPossessions with retain cycle avoided


----------------------------分割线-----------------------

下面是我的理解:

在理解retain cycle形成的过程中有几个原则需要特别注意一下:

1.当Object的referenced counter为0时它会调用自己的dealloc()来企图销毁自己,也就是释放这块内存;

2.在调用dealloc()中会调用它的child object的release方法,目的是将它的child object的referenced counter减一,否则它把自己释放了,但是它曾经使用过的child object的referenced counter一直不能跟着更新,那么它的child object就会一直不能释放,因为referenced counter不能为0,而这是不能允许存在的情况,这叫做memory leak。




其实在上面的这篇原文中也提及到了一点,就是当使用关键字__weak来修改一个变量时,除了不会影响referenced object‘s counter(即它所指向的那个对象的引用计数。如果使用__strong的话就会被所指向的对象中的ounter加1)之外,还有一个智能的地方,就是当这个变量本身的referenced counter变为0时,我们伟大的ARC会自动给这个变量赋上nil,也就是回收了。关于这一点的解释在上面的参考中有实际的代码可以测试,我就贴个图吧:


注意看当打印[calculator container]的时候,实际的打印信息显示了Container:(null)。

关于这一点在apple的官方文档中也有描述,链接地址为:点击打开链接, 具体的内容为下面的描述,我好想什么都没干,就光copy和plast了。

use strong references for their synthesized instance variables. To declare a weak reference, add an attribute to the property, like this:

@property (weak) id delegate;

Note: The opposite to weak is strong. There’s no need to specify the strong attribute explicitly, because it is the default.

Local variables (and non-property instance variables) also maintain strong references to objects by default. This means that the following code will work exactly as you expect:

    NSDate *originalDate = self.lastModificationDate;
    self.lastModificationDate = [NSDate date];
    NSLog(@"Last modification date changed from %@ to %@",
                        originalDate, self.lastModificationDate);

In this example, the local variable originalDate maintains a strong reference to the initial lastModificationDate object. When thelastModificationDate property is changed, the property no longer keeps a strong reference to the original date, but that date is still kept alive by theoriginalDate strong variable.

Note: A variable maintains a strong reference to an object only as long as that variable is in scope, or until it is reassigned to another object or nil.

看完后得知,本地变量其实就是默认的strong类型,如果正巧需要一个strong类型的就没有必要再用"__strong"来修改了,直接写就得了。

只有当需要__weak时是需要特别修改的。

对上面的示例代码解释一下,以防我日后哪天给忘了。

定义了一个originalDate的指针,默认是strong类型的,就是说它会把被它引用的对象的referenced counter加1.

定义完了之后被self.lastModificationDate初始化,而lastModificationDate其实也是指向一个具体的对象的,比如叫xxDate吧,它描述的应该是时间日期这些东西。这样初始化一下子之后,xxDate的referenced counter就会因为originalDate的引用被加1。假设此时xxDate只有它们两个引用,那么referenced counter就是2。然后呢。lastModficicationDate被重新赋值了,重新赋值的意思就是说它不再指向xxDate了,而是指向了一个新的yyDate,这个yyDate代表的是另一个时间日期的描述,这样呢,原来的xxDate的referenced counter就由2减为1了,就是说此时只有originalDate在引用它关心它。如果此时originalDate不是strong类型,而是weak类型的,那么一旦lastModificationDate被重新赋值,那么xxDate的referenced counter就变为0了,因为weak类型的指针不会增加它引用的对象的referenced counter,即在一开始给originalDate赋值的时候,xxDate的referenced counter就一直是1,没有因为多了个引用它的originalDate而增加1,应为originalDate是weak类型的。

明白了上面的这个特点之后,接着就又看到了一个有意思的情况,就是如何给weak类型的变量初始化。可能你会想当然的觉得很简单,上例中初始化代码为:

NSDate *originalDate = self.lastModificationDate;
修改为weak后为:

NSDate __weak *originalDate = self.lastModificationDate;


注意了,如果此时lastModificationDate指向的那个object的referenced counter不是0,那么originalDate是能够指向lastModificationDate指向的那个object的。如果用一个referenced counter是0的object来初始化__weak修改的指针会怎么样?

示例代码:

NSObject * __weak someObject = [[NSObject alloc] init];

当alloc出来一个示例之后发现没人能使它的referenced counter由0增加1,它又会自动被ARC销毁的,且someObject也会被ARC赋值nil。其实质就是someObject就眼睁睁的看着到嘴的鸭子又飞了,这是一种怎么的样感受呢,我们不得而知。

所以呢,人们就想到了一种简单的办法,就是不管someObject能不能引用别人,它自己得先保证自己别被ARC灭了,referenced counter变为0就会被灭,不让它为0就行了,如果不为0呢?就是得找个人假装引用它就行了。

 NSObject *cachedObject = self.someWeakProperty;           // 1
    if (cachedObject) {                                       // 2
        [someObject doSomethingImportantWith:cachedObject];   // 3
    }                                                         // 4
    cachedObject = nil;  


定义一个strong类型的cachedObject来引用someWeakProperty,这样就保证后者的referenced counter不为0了,自保完成,接下来再去干革命。革命完成后就自杀吧。



好了,就先写到这里吧,目前刚看到这里。

-------------分割线-----------

好吧,再接着编辑一下,接下来是使用copy属性的情况,参考的文档还是apple官方的原文。点击打开链接

我的用了原文中的示例代码,源码和运行结果如下图:



原谅我没有把源码以文本的方式贴在帖子里,主要是太懒了,而且没有这样有说服力。

从h文件可以看到声明了两个property,一个是strong属性的firstName,哦对了,strong是默认的,可以不写的;另一个是copy属性的cfirstName;

补充一点,就是不是任何类型都可以使用copy属性的,能够使用它的需要遵从NSCopying协议:

Note: Any object that you wish to set for a copy property must support NSCopying, which means that it should conform to the NSCopying protocol. Protocols are described in Protocols Define Messaging Contracts. For more information on NSCopying, see NSCopying or the Advanced Memory Management Programming Guide.


当使用nameString给它俩赋值的时候可以看出来,它们指向的object地址不同:firstName指向了与nameString相同的对象,而cfirstName则指向了另外一个地址,这个地址应该就是nameString的副本的地址,即nameString的copy的地址,这样一来的话就随便nameString变成什么样了,sfirstName的值都不会改变了。我想等我多个月份以后再回来看看我写篇日记我肯定会说:你行不行啊,连这样的内容都要记下来写成一篇日记,跟每天记三餐都吃什么有什么区别。但是现在的能力不够,只能记一下这些基本的知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值