OC文档之——管理对象间的关系

4.2 Manage the Object Graph through Ownership and Responsibility——通过任务和归属来管理对象关系

技巧:通过自定义的dealloc对象方法,可以看到对象释放的先后顺序,dealloc方法是运行自动调用的,类似于析构函数。


As you’ve already seen, memory for Objective-C objects is allocated dynamically (on the heap), which means you need to use pointers to keep track of an object’s address. Unlike scalar values, it’s not always possible to determine an object’s lifetime by the scope of one pointer variable. Instead, an object must be kept active in memory for as long as it is needed by other objects.

Rather than trying to worry about managing the lifecycle of each object manually, you should instead think about the relationships between objects.

In the case of an XYZPerson object, for example, the two string properties for firstName and lastName are effectively “owned” by the XYZPerson instance. This means they should stay in memory as long as the XYZPerson object stays in memory.

When one object relies on other objects in this way, effectively taking ownership of those other objects, the first object is said to have strong references to the other objects. In Objective-C, an object is kept alive as long as it has at least one strong reference to it from another object. The relationships between the XYZPerson instance and the two NSString objects is shown in Figure 3-2.


如之前所见,OC对象的内存是自动分配的(堆上),也就是说你需要使用指针来追踪对象的地址。对象的生命期不能简单地靠指针变量的作用域来决定,也就是说不能像数值类型那样通过作用域来判断。只要对象上面有其他依赖,那么它就将一直在内存中保存。

在实际编程中,需要关注的是对象之间的关系,而不是尝试去手动管理每个对象的生命期。

XYZPerson的例子中,firstNamelastName这两个属性是被XYZPerson类对象所拥有的,即如果XYZPerson对象生命期未结束,那这两个属性对象的生命期也就不会结束。

当对象间就是这种“拥有”关系时,我们就说第一个对象对另外的对象有“强引用”。在OC中,只要一个对象上还存在其他对象对它的强引用,那么它就不会被自动释放。

比如XYZPerson类对象同它里面的两个NSString类型属性之间的关系就如下图所示:

Figure 3-2 Strong Relationships

3-2


When an XYZPerson object is deallocated from memory, the two string objects will also be deallocated, assuming there aren’t any other strong references left to them.

To add a little more complexity to this example, consider the object graph for an application like that shown in Figure 3-3.


当一个XYZPerson类对象被释放后,这两个字符串对象也会同样被释放(假设它们之上没有其他的强引用)。

再来看一个应用程序中的对象关系例子,如下图所示:

Figure 3-3 The Name Badge Maker application

3-3


When the user clicks the Update button, the badge preview is updated with the relevant name information.

The first time a person’s details are entered and the update button clicked, the simplified object graph might look like Figure 3-4.


当用户点击UPdate时,预览窗口中的badge会更新为用户输入的名字信息。

当第一次输入用户信息并点击按钮后,此时这个应用内的对象关系图如下所示:

Figure 3-4 Simplified object graph for initial XYZPerson creation

3-4


When the user modifies the person’s first name, the object graph changes to look like Figure 3-5.


当用户修改了firstName之后,对象关系图如下所示:

Figure 3-5 Simplified object graph while changing the person’s first name

3-5


The badge display view maintains a strong relationship to the original @”John” string object, even though the XYZPerson object now has a different firstName. This means the @”John” object stays in memory, used by the badge view to print the name.

Once the user clicks the Update button a second time, the badge view is told to update its internal properties to match the person object, so the object graph looks like Figure 3-6.


界面内的badge视图维持了一个指向原来@“john”对象的强引用,即使是现在XYZPerson对象的firstName属性已经被改变了(变成了一个新的NSString对象,因为声明的是不可变的,一旦修改,就会生成一个新的NSString对象)。这就意味着值为john的对象仍然在内存中被badge用来显示名字。

当用户再次点击UPdate按钮,badge视图响应update动作,它内部的字符串对象会被更新为修改后的,如下图所示:

Figure 3-6 Simplified object graph after updating the badge view

3-6


At this point, the original @”John” object no longer has any strong references to it, so it is removed from memory.

By default, both Objective-C properties and variables maintain strong references to their objects. This is fine for many situations, but it does cause a potential problem with strong reference cycles.


这个时候,原来的@“john”字符串对象上已经不存在强引用了,所以它的内存会被释放。

默认情况下,OC的属性和变量都会保持一个指向它们对象的强引用。在对数情况下这都是合理的,但这样可能会出现问题:强引用环。

4.2.1 Avoid Strong Reference Cycles——避免强引用环


Although strong references work well for one-way relationships between objects, you need to be careful when working with groups of interconnected objects. If a group of objects is connected by a circle of strong relationships, they keep each other alive even if there are no strong references from outside the group.

One obvious example of a potential reference cycle exists between a table view object (UITableView for iOS and NSTableView for OS X) and its delegate. In order for a generic table view class to be useful in multiple situations, it delegates some decisions to external objects. This means it relies on another object to decide what content it displays, or what to do if the user interacts with a specific entry in the table view.

A common scenario is that the table view has a reference to its delegate and the delegate has a reference back to the table view, as shown in Figure 3-7.


强引用在对象的单向关系中不存在任何问题,但在相互交织的对象网中,强引用就必须引起足够注意。如果有一组对象构成了一个强引用闭合环,即便是外部已经没有作用于该组对象的强引用,它们之间也会相互支持对方生命期,永远不会被自动释放。

UITableView和它的委托(delegate)之间就可能存在潜在的强引用环。为了保证table view在各种环境中的通用性,它会委托外部对象做出一些决断。即如果UI上面有table view的接口,那么它就会依赖其他对象来决定自身显示什么或做什么。

一个例子就是table view有一个指向它委托的强引用,委托又有一个回指的强引用,如下图所示:

Figure 3-7 Strong references between a table view and its delegate

3-7


A problem occurs if the other objects give up their strong relationships to the table view and delegate, as shown in Figure 3-8.


如果外部对象放弃了对table view和它的委托的强引用,则会出现下面的问题,如图所示:

Figure 3-8 A strong reference cycle

3-8


Even though there is no need for the objects to be kept in memory—there are no strong relationships to the table view or delegate other than the relationships between the two objects—the two remaining strong relationships keep the two objects alive. This is known as a strong reference cycle.


外部对table view及其委托没有强引用的情况下,它们二者都维护了对对方的强引用关系,导致双方都互相维持对方的生命期,这就被称为强引用环(循环)。


The way to solve this problem is to substitute one of the strong references for a weak reference. A weak reference does not imply ownership or responsibility between two objects, and does not keep an object alive.

If the table view is modified to use a weak relationship to its delegate (which is how UITableView and NSTableView solve this problem), the initial object graph now looks like Figure 3-9.


解决强引用循环的一个方法就是:使用弱引用(weak reference)来替代强引用。一个弱引用不会引入对象间的责任和归属关系,也不会维持被引用对象的生命期。
如果table view使用弱引用来指向它的委托(UITableView和NSTableView都是这样做的),那么初始的对象关系图如下所示:

Figure 3-9 The correct relationship between a table view and its delegate

3-9


When the other objects in the graph give up their strong relationships to the table view and delegate this time, there are no strong references left to the delegate object, as shown in Figure 3-10.


当其他对象放弃了对table view和它的委托的强引用之后,那table view的委托对象上就没有强引用了,如下图所示:

Figure 3-10 Avoiding a strong reference cycle

3-10


This means that the delegate object will be deallocated, thereby releasing the strong reference on the table view, as shown in Figure 3-11.

Once the delegate is deallocated, there are no longer any strong references to the table view, so it too is deallocated.


即现在委托对象会被自动释放,一旦委托对象被释放,它对table view的强引用也随之消失,则table view也会被自动释放。如下图所示:

Figure 3-11 Deallocating the delegate

3-11


4.2.2 Use Strong and Weak Declarations to Manage Ownership——使用strong和weak声明管理对象关系

By default, object properties declared like this:

默认情况下,对象属性声明格式如下:

@property id delegate;

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.

注意:和weak对于的是strong。在OC中不需要单独指定属性的strong修饰符,因为默认情况下就是strong。

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 the lastModificationDate property is changed, the property no longer keeps a strong reference to the original date, but that date is still kept alive by the originalDate strong variable.

上述代码中,本地变量originalDatelastModificationDate属性对应的初始对象是强引用关系。当lastModificationDate属性被改变后,属性不再同它的初始对象有强引用关系,但是原来对象仍然存在于内存中,原因就是originalDate变量仍然对其有强引用。

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.

一个变量对于对象的强引用只能持续到这个变量作用域结束,或者是这个变量指向另外的值,或者它被设置为nil。


If you don’t want a variable to maintain a strong reference, you can declare it as __weak, like this:

如果不希望一个本地变量对某个对象有强引用,则可在声明变量时使用__weak:

NSObject * __weak weakVariable;

Because a weak reference doesn’t keep an object alive, it’s possible for the referenced object to be deallocated while the reference is still in use. To avoid a dangerous dangling pointer to the memory originally occupied by the now deallocated object, a weak reference is automatically set to nil when its object is deallocated.

因为一个弱引用不会保存对象存在,所以有可能当弱引用仍然存在的时候,被引用的对象已经被释放。为避免出现野指针(dangling pointer,即指向不确定内容的指针,当指针仍然指向该内存区域时,它所指向的对象已经被释放),当一个弱引用指向的对象被释放后,它会被自动设置成nil。

This means that if you use a weak variable in the previous date example:

这就是说如果你在前面的date例子中使用weak变量的话:

NSDate * __weak originalDate = self.lastModificationDate;

self.lastModificationDate = [NSDate date];

the originalDate variable may potentially be set to nil. When self.lastModificationDate is reassigned, the property no longer maintains a strong reference to the original date. If there are no other strong references to it, the original date will be deallocated and originalDate set to nil.

self.lastModificationDate被重新赋值后,变量originalDate可能会被自动设置成nil。因为属性不在维持对原始日期对象的强引用。如果原始日期对象上没有其他强引用的话,那这个对象就会被释放,则弱引用变量originalDate就会被设置为nil。

Weak variables can be a source of confusion, particularly in code like this:

有时弱引用会引起歧义,尤其是像下面的代码:

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

In this example, the newly allocated object has no strong references to it, so it is immediately deallocated and someObject is set to nil.

上面代码中,新分配的对象上并不存在强引用,所以它一创建出来就被释放,someObject变量便被设置成了nil。

Note: The opposite to __weak is __strong. Again, you don’t need to specify __strong explicitly, because it is the default.

注意:和weak相对的是strong,你不必显示声明__strong,因为它是默认的。

It’s also important to consider the implications of a method that needs to access a weak property several times, like this:

当一个方法需要对弱引用属性进行若干次访问的时候需要仔细考虑:

- (void)someMethod {
    [self.weakProperty doSomething];
    ...
    [self.weakProperty doSomethingElse];
}

In situations like this, you might want to cache the weak property in a strong variable to ensure that it is kept in memory as long as you need to use it:

如果像上面这个情况,那就需要把弱引用属性先用一个强引用变量保存,这样才能保证这个属性对应对象不会被错误释放。

- (void)someMethod {
    NSObject *cachedObject = self.weakProperty;
    [cachedObject doSomething];
    ...
    [cachedObject doSomethingElse];
}

In this example, the cachedObject variable maintains a strong reference to the original weak property value so that it can’t be deallocated as long as cachedObject is still in scope (and hasn’t been reassigned another value).

It’s particularly important to keep this in mind if you need to make sure a weak property is not nil before using it. It’s not enough just to test it, like this:

这个例子中,cachedObject变量对weak属性对应的对象有强引用。只要cachedObject还在作用域内(并且没有被重新赋值),这个属性对象就不会被自动释放。

要记住,如果你需要保证一个弱引用属性在使用时不是nil,就需要像上面那样做。仅仅使用if来测试是不够的,如下所示:

if (self.someWeakProperty) {
        [someObject doSomethingImportantWith:self.someWeakProperty];
    }

because in a multi-threaded application, the property may be deallocated between the test and the method call, rendering the test useless. Instead, you need to declare a strong local variable to cache the value, like this:

因为在一个多线程程序中,属性对应的对象可能会在if测试和方法调用的中间时间段内被释放(if测试时不是nil,但当方法调用时对象已经被释放了),这样就使得if测试毫无用处。所以,最保险的办法是使用一个本地cache变量来对这个弱引用属性对象加上强引用关系,如下所示:

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

In this example, the strong reference is created in line 1, meaning that the object is guaranteed to be alive for the test and method call. In line 5, cachedObject is set to nil, thereby giving up the strong reference. If the original object has no other strong references to it at this point, it will be deallocated and someWeakProperty will be set to nil.

上面例子里,第一行就是对属性对象加入强引用,这样可以保证属性对象在if测试和方法调用后仍然存在。第五行将cachedObject设置成了nil,则强引用消失。如果属性对象上此时没有强引用,则它会被释放,而属性someWeakProperty将会被设置成nil。

4.2.3 Use Unsafe Unretained References for Some Classes——在某些类中使用unsafe_unretained引用

There are a few classes in Cocoa and Cocoa Touch that don’t yet support weak references, which means you can’t declare a weak property or weak local variable to keep track of them. These classes include NSTextView, NSFont and NSColorSpace; for the full list, see Transitioning to ARC Release Notes.

If you need to use a weak reference to one of these classes, you must use an unsafe reference. For a property, this means using the unsafe_unretained attribute:

在Cocoa和Cocoa Touch框架中的某些类还没有对weak引用的支持,即无法在这些类中使用weak属性,也无法定义weak变量来追踪这些类的对象。这些类包括NSTextView、NSFont、NSColorSpace等。详细列表详见Transitioning to ARC Release Notes。

如果需要对这些类使用弱引用,必须使用unsafe引用。对于类属性来说,就是使用unsafe_unretained属性修饰符。

@property (unsafe_unretained) NSObject *unsafeProperty;

For variables, you need to use __unsafe_unretained:

对于变量来说,则是使用__unsafe _unretained修饰:

NSObject * __unsafe_unretained unsafeReference;

An unsafe reference is similar to a weak reference in that it doesn’t keep its related object alive, but it won’t be set to nil if the destination object is deallocated. This means that you’ll be left with a dangling pointer to the memory originally occupied by the now deallocated object, hence the term “unsafe.” Sending a message to a dangling pointer will result in a crash.

一个unsafe引用和weak引用的相似之处是它与对象生命期无关,不同的是,unsafe引用当它指向的对象释放后,它不会被自动设置为nil。即它可能会成为野指针,这也是把它称为unsafe的原因,向一个野指针发送消息会导致crash。

4.2.4 Copy Properties Maintain Their Own Copies

In some circumstances, an object may wish to keep its own copy of any objects that are set for its properties.

As an example, the class interface for the XYZBadgeView class shown earlier in Figure 3-4 might look like this:

某些情况下,一个对象可能希望保存任何被置为它的属性的对象的复制。

例如图3-4中所示的XYZBadgeView类接口可能如下所示:

@interface XYZBadgeView : NSView
@property NSString *firstName;
@property NSString *lastName;
@end

Two NSString properties are declared, which both maintain implicit strong references to their objects.

Consider what happens if another object creates a string to set as one of the badge view’s properties, like this:

类中声明了两个NSString类属性,两个属性都和它的对象是强引用关系。

如果另外又创建了一个badge view的字符串属性对象,如下所示:

    NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
    self.badgeView.firstName = nameString;

This is perfectly valid, because NSMutableString is a subclass of NSString. Although the badge view thinks it’s dealing with an NSString instance, it’s actually dealing with an NSMutableString.

This means that the string can change:

上面的代码肯定是可行的,因为NSMutableString是NSString的子类,badge view会认为它是在处理NSString对象,而实际上该对象是NSMutableString类型的,这也意味着该字符串对象可以被修改:

[nameString appendString:@"ny"];

In this case, although the name was “John” at the time it was originally set for the badge view’s firstName property, it’s now “Johnny” because the mutable string was changed.

You might choose that the badge view should maintain its own copies of any strings set for its firstName and lastName properties, so that it effectively captures the strings at the time that the properties are set. By adding a copy attribute to the two property declarations:

这个情况下,虽然第一次将badge view的firstName属性设置成的是“John”,但现在在外部被修改为了“Johnny”。

可以选择让badge view维护它自己的firstName和lastName字符串属性的拷贝,故它可以有效地保持设置属性时的字符串值。要使用拷贝,只需要在属性修饰符内加入copy:

@interface XYZBadgeView : NSView
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
@end

the view now maintains its own copies of the two strings. Even if a mutable string is set and subsequently changed, the badge view captures whatever value it has at the time it is set. For example:

现在badge view就在维护它的两个属性的拷贝了。即使当一个可变字符串被设置到它上面,并且被修改了,它里面的属性值仍然是被设置时的值,如下所示:

    NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
    self.badgeView.firstName = nameString;
    [nameString appendString:@"ny"];

This time, the firstName held by the badge view will be an unaffected copy of the original “John” string.

The copy attribute means that the property will use a strong reference, because it must hold on to the new object it creates.

这种情况下,firstName属性指向的是原始“John”字符串的对象的拷贝,这个拷贝不会被外部影响。

copy属性修饰符意味着属性对它的对象是强引用关系,因为属性必须保证在没有创建新对象之前,它原来指向的对象不会被释放。

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.

注意:任何你想设置copy的属性类型都必须支持NSCopying协议。

If you need to set a copy property’s instance variable directly, for example in an initializer method, don’t forget to set a copy of the original object:

如果你想直接设置一个拷贝属性的实例变量,比如在初始化方法中,不要忘了为原来的对象设置拷贝。

- (id)initWithSomeOriginalString:(NSString *)aString {
    self = [super init];
    if (self) {
        _instanceVariableForCopyProperty = [aString copy];
    }
    return self;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值