• Programming With Objective-C---- Encapsulating Data ---- Objective-C 学习(三) 封装数据


     

    Programming with Objective-C

    Encapsulating Data

    In addition to the messaging behavior covered in the previous chapter, an object also encapsulates data through its properties.

    除了前一章讲述的消息方法(messaging behavior), 对象还能通过它的特性(properties)来封装数据。

    This chapter describes the Objective-C syntax used to declare properties for an object and explains how those properties are implemented by default through synthesis of accessor methods and instance variables. If a property is backed by an instance variable, that variable must be set correctly in any initialization methods.

    本章讲述声明对象特性的Objective-C语法,以及如何通过访问器合成方法和实例变量来实现那些特性。如果某个特性由一个实例变量备份,那么该变量必须在所有初始化方法里都设置正确。

    If an object needs to maintain a link to another object through a property, it’s important to consider the nature of the relationship between the two objects. Although memory management for Objective-C objects is mostly handled for you through Automatic Reference Counting (ARC), it’s important to know how to avoid problems like strong reference cycles, which lead to memory leaks. This chapter explains the lifecycle of an object, and describes how to think in terms of managing your graph of objects through relationships.

    如果一个对象需要通过一个特性来保持对另一个对象的链接,考虑两个对象之间关系的性质是很重要的。 尽管Object-C的自动索引计数(ARC)已经帮我们处理了大部分的内存管理, 但是了解如何避免像强引用周期(strong reference cycles)这样的问题也很重要,它能引起内存泄露。 本章讲述一个对象的生命周期,并描述如何通过管理对象关系图来思考问题。

    Properties Encapsulate an Object’s Values

    一、用特性来封装对象的值

    Most objects need to keep track of information in order to perform their tasks. Some objects are designed to model one or more values, such as a Cocoa NSNumberclass to hold a numeric value or a custom XYZPerson class to model a person with a first and last name. Some objects are more general in scope, perhaps handling the interaction between a user interface and the information it displays, but even these objects need to keep track of user interface elements or the related model objects.

    大多数对象需要跟踪它们的信息用来执行它们的任务。 一些对象被设计成用来塑造一个或多个值,比如Cocoa NSNumber类,它可以用来保存一个数字,又比如一个自定义的XYZPerson类,用来塑造一个带有first 和 last name的person。 其它一些对象作用更加一般,或许是用来处理用户界面和它显示的内容之间的交互,但是即使是这些对象也需要跟踪用户界面元素或者相关的模型对象。

    Declare Public Properties for Exposed Data

    1、为需要暴露的数据声明公共特性

    Objective-C properties offer a way to define the information that a class is intended to encapsulate. As you saw in Properties Control Access to an Object’s Values, property declarations are included in the interface for a class, like this:

    Objective-C特性提供了一种方法来定义一个类需要封装的信息。正如你在Properties Control Access to an Object’s Values中所见,特性声明在类声明的interface中进行,如下:

    @interface XYZPerson : NSObject
    @property NSString *firstName;
    @property NSString *lastName;
    @end

    In this example, the XYZPerson class declares string properties to hold a person’s first and last name.

    在上例中,XYZPerson类声明了字符串特性来保存一个person的first name 和 last name.

    Given that one of the primary principles in object-oriented programming is that an object should hide its internal workings behind its public interface, it’s important to access an object’s properties using behavior exposed by the object rather than trying to gain access to the internal values directly.

    面向对象编程的一个重要原则是对象应该通过公共接口,把它的内部工作隐藏起来。 使用对象提供的方法来访问对象的特性,而不是直接访问内部值是很重要的。

    Use Accessor Methods to Get or Set Property Values

    2、使用访问器方法来获取和设置特性值

    You access or set an object’s properties via accessor methods:

    你可以通过访问器方法访问或设置对象的特性。

        NSString *firstName = [somePerson firstName];
        [somePerson setFirstName:@"Johnny"];

    By default, these accessor methods are synthesized automatically for you by the compiler, so you don’t need to do anything other than declare the property using@property in the class interface.

    一般情况下,编译器会自动合成这些访问器方法,所以只需要在类的interface中使用@property来声明特性,不需要在做别的。

    The synthesized methods follow specific naming conventions:

    合成方法遵循以下特定的命名规则:

    • The method used to access the value (the getter method) has the same name as the property.

      访问特性值的方法(getter 方法),方法名跟特性名一样。

      The getter method for a property called firstName will also be called firstName.

      如果特性名为firstName,则getter方法名也为firstName.

    • The method used to set the value (the setter method) starts with the word “set” and then uses the capitalized property name.

      设置特性值的方法(setter方法),方法名以“set”开头,然后跟着首字母大写的特性名。

      The setter method for a property called firstName will be called setFirstName:.

      如果特性名为firstName, 那么设置该特性的方法名为setFirstName.

    If you don’t want to allow a property to be changed via a setter method, you can add an attribute to a property declaration to specify that it should be readonly:

    如果你不想让用户通过setter方法更改特性,你可以在特性声明中添加一个readonly属性来指明:

    @property (readonly) NSString *fullName;

    As well as showing other objects how they are supposed to interact with the property, attributes also tell the compiler how to synthesize the relevant accessor methods.

    In this case, the compiler will synthesize a fullName getter method, but not a setFullName: method.

    除了告诉其它对象如何跟特性交互,属性还编译器如何合成相关的访问器方法。语句 @property (readonly) NSString *fullName; 编译器将会合成一个名为fullName 的getter方法,但不会合成setFullName:方法。

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

    注意:readonly的反义词是readwrite. 不需要指定readwrite属性,默认情况下已经指定。

    If you want to use a different name for an accessor method, it’s possible to specify a custom name by adding attributes to the property. In the case of Boolean properties (properties that have a YES or NO value), it’s customary for the getter method to start with the word “is.” The getter method for a property calledfinished, for example, should be called isFinished.

    如果你想给访问器方法指定一个不同的方法,可以给特性增加一个自定义名称的属性。布尔值特性(特性值为YES 或 NO), 按照惯例getter方法以"is"开头。 比如,如果一个特性名位finished, 它的getter方法名应该为isFinished.

    Again, it’s possible to add an attribute on the property:

    再次说明,可以给特性增加一个如下属性:

    @property (getter=isFinished) BOOL finished;

    If you need to specify multiple attributes, simply include them as a comma-separated list, like this:

    如果你需要指定多个属性,只需用逗号分隔各个属性即可,如下:

    @property (readonly, getter=isFinished) BOOL finished;

    In this case, the compiler will synthesize only an isFinished method, but not a setFinished: method.

    在这种情况下,编译器只会生产一个名为isFinished的getter方法,并不会生成setFinished:方法。

    Note: In general, property accessor methods should be Key-Value Coding (KVC) compliant, which means that they follow explicit naming conventions.

    See Key-Value Coding Programming Guide for more information.

    注意:通常,特性访问器方法应该遵从键值编码(KVC)规则,就是说他们应该遵守明确的命名规则。参照Key-Value Coding Programming Guide 获取更多信息

    Dot Syntax Is a Concise Alternative to Accessor Method Calls

    3. 调用访问器方法时可以用"."号句法

    As well as making explicit accessor method calls, Objective-C offers an alternative dot syntax to access an object’s properties.

    Dot syntax allows you to access properties like this:

    除了使用访问器方法访问特性外,Object-C还提供了一种"."号句法来访问对象的特性。

        NSString *firstName = somePerson.firstName;
        somePerson.firstName = @"Johnny";

    Dot syntax is purely a convenient wrapper around accessor method calls. When you use dot syntax, the property is still accessed or changed using the getter and setter methods mentioned above:

    "."句法仅仅是访问器方法的一种便利包装。当你使用点句法时,特性任然是使用getter 和 setter方法来操作的。

    • Getting a value using somePerson.firstName is the same as using [somePerson firstName]
      somePerson.firstName == [somePerson firstName]

    • Setting a value using somePerson.firstName = @"Johnny" is the same as using [somePerson setFirstName:@"Johnny"]
      somePerson.firstName = @"Johnny" 等同于 [somePerson setFirstName:@"Johnny"]

    This means that property access via dot syntax is also controlled by the property attributes. If a property is marked readonly, you’ll get a compiler error if you try to set it using dot syntax.

    这就是说通过点句法访问特性还是受特性属性所掌控。 如果某个特性带有readonly属性,当你用点句法给特性设置值时会产生一个编译器错误。

    Most Properties Are Backed by Instance Variables

    4. 大多数特性由实例变量支持

    By default, a readwrite property will be backed by an instance variable, which will again be synthesized automatically by the compiler.

    默认情况下,一个readwrite特性将会由一个实例变量备份,它会被编译器再次自动合成。

    An instance variable is a variable that exists and holds its value for the life of the object. The memory used for instance variables is allocated when the object is first created (through alloc), and freed when the object is deallocated.

    实例变量是一种可以在整个对象周期内存在并能保存其值的变量。实例变量的内存在对象第一次被创建时(通过alloc方法)分配,对象被释放时才释放。

    Unless you specify otherwise, the synthesized instance variable has the same name as the property, but with an underscore prefix. For a property called firstName, for example, the synthesized instance variable will be called _firstName.

    除非你另外指定,合成的实例变量名跟特性名相同,但是实例变量名有一个下划线前缀。 比如,特性名为firstName,合成的实例变量名为_firstName.

    Although it’s best practice for an object to access its own properties using accessor methods or dot syntax, it’s possible to access the instance variable directly from any of the instance methods in a class implementation. The underscore prefix makes it clear that you’re accessing an instance variable rather than, for example, a local variable:

    尽管用访问器方法或点句法访问一个对象的特性是最好的方法,但是从一个类实现里的任何实例方法中直接访问实例变量也是可能的。下划线前缀让你清楚明白你访问的是一个实例变量,而不是一个本地变量:

    - (void)someMethod {
        NSString *myString = @"An interesting string";
     
        _someString = myString;
    }

    In this example, it’s clear that myString is a local variable and _someString is an instance variable.

    在这里,很清楚myString是一个本地变量,而_someString是一个实例变量。

    In general, you should use accessor methods or dot syntax for property access even if you’re accessing an object’s properties from within its own implementation, in which case you should use self:

    通常,你应该使用访问器方法或点句法来访问特性,即使你是在对象的实现中访问它自己的特性,在这种情况下你应该使用self关键字:

    - (void)someMethod {
        NSString *myString = @"An interesting string";
     
        self.someString = myString;
      // or
        [self setSomeString:myString];
    }

    The exception to this rule is when writing initialization, deallocation or custom accessor methods, as described later in this section.

    稍后会介绍该规则的例外情况,即当你编写初始化,重新分配地址或自定义访问器方法时。

    You Can Customize Synthesized Instance Variable Names

    5. 你可以自定义合成实例变量名

    As mentioned earlier, the default behavior for a writeable property is to use an instance variable called _propertyName.

    正如前面所述,一个可写特性的默认行为是使用一个叫做_propertyName的实例变量。

    If you wish to use a different name for the instance variable, you need to direct the compiler to synthesize the variable using the following syntax in your implementation:

    如果你希望让实例变量使用一个不同的名字,你需要指导编译器按照你在implementation中的语法来合成变量。

    @implementation YourClass
    @synthesize propertyName = instanceVariableName;
    ...
    @end

    For example:

    @synthesize firstName = ivar_firstName;

    In this case, the property will still be called firstName, and be accessible through firstName and setFirstName: accessor methods or dot syntax, but it will be backed by an instance variable called ivar_firstName.

    在这种情况下,特性名还是firstName, 它可以通过firstName 和setFirstName方法或点句法来访问。但是它将由一个名为ivar_firstName的实例变量来备份。

    Important: If you use @synthesize without specifying an instance variable name, like this:

    重要提示:如果你使用@synthesize方法,但是却没有指定一个实例变量名,如下:

    @synthesize firstName;
    the instance variable will bear the same name as the property.

     那么,实例变量名跟特性名一样。

    In this example, the instance variable will also be called firstName, without an underscore.

     在这里,实例变量名还是firstName, 没有下划线。

    You Can Define Instance Variables without Properties

    6. 不用特性你也可以定义实例变量

    It’s best practice to use a property on an object any time you need to keep track of a value or another object.

    If you do need to define your own instance variables without declaring a property, you can add them inside braces at the top of the class interface or implementation, like this:

    任何时候如果一个对象需要跟踪一个值或另一个对象,使用特性都是一个最佳方法。

    如果你确定需要定义自己的实例变量而不用声明特性,你可以在interface 或 implementation 的顶部,大括号中声明它们。请看下面代码:

    @interface SomeClass : NSObject {
        NSString *_myNonPropertyInstanceVariable;
    }
    ...
    @end
     
    @implementation SomeClass {
        NSString *_anotherCustomInstanceVariable;
    }
    ...
    @end

    Note: You can also add instance variables at the top of a class extension, as described in Class Extensions Extend the Internal Implementation.

     注意:你还可以在一个类扩展的顶部添加实例变量,请看 Class Extensions Extend the Internal Implementation 中的描述。

    Access Instance Variables Directly from Initializer Methods

    7. 从初始化方法中直接访问实例变量

    Setter methods can have additional side-effects. They may trigger KVC notifications, or perform further tasks if you write your own custom methods.

    Setter方法可以有额外的边缘效应(side-effects). 如果你编写自己的自定义方法,它们或许能触发KVC(key-value coding)通知, 或者可以执行进一步的任务。

    You should always access the instance variables directly from within an initialization method because at the time a property is set, the rest of the object may not yet be completely initialized. Even if you don’t provide custom accessor methods or know of any side effects from within your own class, a future subclass may very well override the behavior.

    你应该总是在一个初始化方法中访问实例变量,因为当特性值被设置时,其它对象有可能都还没有完成初始化。 即使你不提供自定义访问器方法,或者了解你自己的类的所有边缘效应,以后它的子类也有可能很好的重载该行为。

    A typical init method looks like this:

    一个典型的init方法如下:

    - (id)init {
        self = [super init];
     
        if (self) {
            // initialize instance variables here
        }
     
        return self;
    }

    An init method should assign self to the result of calling the superclass’s initialization method before doing its own initialization. A superclass may fail to initialize the object correctly and return nil so you should always check to make sure self is not nil before performing your own initialization.

    init方法应该先把超类的初始化方法赋值给self, 然后做自己的初始化。 超类可能不能初始化成功并把nil赋值给self,所以你应该总是在执行自己的初始化之前判断self不是nil。

    By calling [super init] as the first line in the method, an object is initialized from its root class down through each subclass init implementation in order. Figure 3-1 shows the process for initializing an XYZShoutingPerson object.

    通过在第一行调用[super init], 对象通过每个子类的init方法依次从他的根类那完成初始化。Figure 3-1 显示了初始化一个XYZShoutingPerson对象的完整过程。

    Figure 3-1  The initialization process

    As you saw in the previous chapter, an object is initialized either by calling init, or by calling a method that initializes the object with specific values.

    正如前一章所述,一个对象既可以通过调用init方法初始化,也可以调用初始化方法给对象来给对象设置一些初始值。

    In the case of the XYZPerson class, it would make sense to offer an initialization method that set the person’s initial first and last names:

    在XYZPerson类中,可以提供一个带有person的初始first name 和 last name 参数的初始化方法:

    - (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName;

    You’d implement the method like this:

    你可以如下实现该方法:

    - (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
        self = [super init];
     
        if (self) {
            _firstName = aFirstName;
            _lastName = aLastName;
        }
     
        return self;
    }

    The Designated Initializer is the Primary Initialization Method

    8. 指定的初始化方法是最基本的初始化方法

    If an object declares one or more initialization methods, you should decide which method is the designated initializer. This is often the method that offers the most options for initialization (such as the method with the most arguments), and is called by other methods you write for convenience. You should also typically overrideinit to call your designated initializer with suitable default values.

    如果一个对象声明了一个或多个初始化方法,你应该决定哪个是指定的初始化方法。 该方法常常是为初始化提供最多选项的方法(比如带有最多参数的方法),它能让别的方法很方便地调用。通常你还可以重载init方法来调用指定初始化方法来设置一些合适的默认值。

    If an XYZPerson also had a property for a date of birth, the designated initializer might be:

    如果一个XYZPerson类还有一个出生日期特性,指定的初始化方法可能是:

    - (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName
                                                dateOfBirth:(NSDate *)aDOB;

    This method would set the relevant instance variables, as shown above. If you still wished to provide a convenience initializer for just first and last names, you would implement the method to call the designated initializer, like this:

    该方法或许设置了相关的实例变量。如果你还希望只给first name 和 last name提供一种方便的初始化方法, 你可以像下面代码一样调用指定初始化方法来实现。

    - (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
        return [self initWithFirstName:aFirstName lastName:aLastName dateOfBirth:nil];
    }

    You might also implement a standard init method to provide suitable defaults:

    或许,你还想要实现一个标准的init方法来提供合适的默认值:

    - (id)init {
        return [self initWithFirstName:@"John" lastName:@"Doe" dateOfBirth:nil];
    }

    If you need to write an initialization method when subclassing a class that uses multiple init methods, you should either override the superclass’s designated initializer to perform your own initialization, or add your own additional initializer. Either way, you should call the superclass’s designated initializer (in place of[super init];) before doing any of your own initialization.

    当你需要在使用多个init方法来创建一个子类的初始化方法时,你应该重载超类的指定初始化程序来完成你的初始化,或者编写自己额外的初始化程序。不管是哪种方法,你都应该先调用超类的指定初始化程序(使用[super init];) ,然后再做自己的初始化工作。

    You Can Implement Custom Accessor Methods

    9. 你可以实现自定义访问器方法

    Properties don’t always have to be backed by their own instance variables.

    特性并不总是由它们自己的实例变量备份。

    As an example, the XYZPerson class might define a read-only property for a person’s full name:

    举个例子,XZYPerson类可能定义了一个只读特性,特性名为fullName:

    @property (readonly) NSString *fullName;

    Rather than having to update the fullName property every time the first or last name changed, it would be easier just to write a custom accessor method to build the full name string on request:

    比起每次first name 和 last name 发生改变,fullName特性也跟着更新, 自己写一个访问器方法会更加简单。该方法中可以根据需要构建full name.

    - (NSString *)fullName {
        return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
    }

    This simple example uses a format string and specifiers (as described in the previous chapter) to build a string containing a person’s first and last names separated by a space.

    这个简单的例子使用了一个格式化字符串和说明符(前章有描述),创建了一个包含first name 和 last name 的字符串,first name 和last name 中间有空格。

    Note: Although this is a convenient example, it’s important to realize that it’s locale-specific, and is only suitable for use in countries that put a person’s given name before the family name.

    注意:尽管这是一个方便的例子,但是它很本地化,它只适合那些名字写在姓前面的国家。

    If you need to write a custom accessor method for a property that does use an instance variable, you must access that instance variable directly from within the method. For example, it’s common to delay the initialization of a property until it’s first requested, using a “lazy accessor,” like this:、

    如果你需要使用实例变量的自定义访问器方法,你必须直接在该方法里访问实例变量。比如,通常延迟对特性做初始化直到它第一次被请求,可以使用一个“lazy 访问器方法”,请看下方方法:

    - (XYZObject *)someImportantObject {
        if (!_someImportantObject) {
            _someImportantObject = [[XYZObject alloc] init];
        }
     
        return _someImportantObject;
    }

    Before returning the value, this method first checks whether the _someImportantObject instance variable is nil; if it is, it allocates an object.

    返回_someImportantObject值之前,方法首先检查该实例变量是不是nil,如果是nil,就给这个对象分配地址。

    Note: The compiler will automatically synthesize an instance variable in all situations where it’s also synthesizing at least one accessor method. If you implement both a getter and a setter for a readwrite property, or a getter for a readonly property, the compiler will assume that you are taking control over the property implementation and won’t synthesize an instance variable automatically.

    注意:编译器在任何情况下都会自动合成一个实例变量,同时也自动合成至少一个访问器方法。 如果你为一个readwrite特性实现了一个getter 方法和一个setter方法,或者为一个readonly特性实现了一个getter方法,编译器会假设你像全权掌控特性实现,这样它就不会自动合成一个实例变量。

    If you still need an instance variable, you’ll need to request that one be synthesized:

    如果你任然需要一个实例变量,你需要通过@synthesize请求一个。

    @synthesize property = _property;

    Properties Are Atomic by Default

    10. 特性默认属性都是Atomic

    By default, an Objective-C property is atomic:

    默认情况下,一个Objective-C特性的属性是atomic:

    @interface XYZObject : NSObject
    @property NSObject *implicitAtomicObject;          // atomic by default
    @property (atomic) NSObject *explicitAtomicObject; // explicitly marked atomic
    @end

    This means that the synthesized accessors ensure that a value is always fully retrieved by the getter method or fully set via the setter method, even if the accessors are called simultaneously from different threads.

    这意味着合成的访问器方法能保证一个值总是能过通过getter方法获取,或者总是能通过setter方法设置特性的值,即使从不同的线程同时调用这些访问器方法。

    Because the internal implementation and synchronization of atomic accessor methods is private, it’s not possible to combine a synthesized accessor with an accessor method that you implement yourself. You’ll get a compiler warning if you try, for example, to provide a custom setter for an atomicreadwrite property but leave the compiler to synthesize the getter.

    因为原子(atomic)访问器方法的内部实现和同步都是私有的,所以把一个合成的访问器方法和一个自定义的访问器方法合并是不可行的。 当你尝试合并时,举个例子,给一个atomic ,readwrite特性提供一个自定义setter方法,并且让编译器合成getter方法时,编译器会给出一个编译器警告错误。

    You can use the nonatomic property attribute to specify that synthesized accessors simply set or return a value directly, with no guarantees about what happens if that same value is accessed simultaneously from different threads. For this reason, it’s faster to access a nonatomic property than an atomic one, and it’s fine to combine a synthesized setter, for example, with your own getter implementation:

    你可以使用nonatomic特性属性来设置---合成访问器只需简单地直接设置或返回一个值,不管其它对象从不同的线程访问该值时是否得到相同的值。因此访问一个nonatomic特性比访问一个atomic特性更快, nonatomic特性可以把一个合成的setter方法和自定义的getter实现合并使用。

    @interface XYZObject : NSObject
    @property (nonatomic) NSObject *nonatomicObject;
    @end
    @implementation XYZObject
    - (NSObject *)nonatomicObject {
        return _nonatomicObject;
    }
    // setter will be synthesized automatically
    @end

    Note: Property atomicity is not synonymous with an object’s thread safety.

    注意:特性的原子性不等同于一个对象的线程安全。

    Consider an XYZPerson object in which both a person’s first and last names are changed using atomic accessors from one thread. If another thread accesses both names at the same time, the atomic getter methods will return complete strings (without crashing), but there’s no guarantee that those values will be the right names relative to each other. If the first name is accessed before the change, but the last name is accessed after the change, you’ll end up with an inconsistent, mismatched pair of names.

    考虑一下,一个XYZPerson对象,使用原子访问器方法从一个线程设置它的first name 和 last name。 同时另一个线程访问器方法也在访问first name 和 last name, 原子getter方法会毫不出错地返回字符串,但是不保证返回的值就是正确的first name 和 last name。 如果first name 在改变之前被访问,而last name 在改变后被访问,你将得到一组不正确的名字。

    This example is quite simple, but the problem of thread safety becomes much more complex when considered across a network of related objects. Thread safety is covered in more detail in Concurrency Programming Guide.

    这个例子相当简单,但是线程安全的问题在考虑跨网络的相关对象时会变得更加复杂。 有关线程安全的内容,在Concurrency Programming Guide 中有详细讲述。

    Manage the Object Graph through Ownership and Responsibility

    二、通过所有关系和责任关系管理对象图

    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.

    正如你所见,Object-C对象的内存都是自动分配的,它们被存储在heap(堆)中,这就说明你需要使用指针来跟踪一个对象的地址。 不像标量值(scalar values),对象它并不总是能够通过一个指针变量的范围来确定对象的生命周期。相反,一个对象必须一直在内存中保持活跃,只要别的对象还需要它。

    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.

    举个例子,就像一个XYZPerson对象,firstName 和lastName 这两个字符串特性是属于("owned")XYZPerson 实例的。 这就是说这些特性的生命周期跟XYZPerson对象的生命周期一样,只要XYZPerson对象还存在于内存中,这些特性就也存在。

    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 XYZPersoninstance and the two NSString objects is shown in Figure 3-2.

    当一个对象以这种方式依赖于其它对象时,就能有效的掌握那些其它对象的所有关系--也就是说第一个对象拥有对其它对象的强引用(strong references)。在Object-C中,只要有别的对象对该对象有一个强引用,该对象就能在内存保持活跃。 图3-2为XYZPerson实例和两个NSString对象之间的关系。

    Figure 3-2  Strong Relationships

    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对象被释放时,这两个字符串对象也将被释放,假设已经不存在别的对象对XYZPerson对象的强引用。给该例子稍微增加点复杂性,考虑图3-3中显示的应用对象图。

    Figure 3-3  The Name Badge Maker application

    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.

    当用户点击了更新按钮,Preview区就会显示相关的名字信息。

    第一次输入一个person的名字,并点击更新按钮这个过程的简化对象图如图3-4.

    Figure 3-4  Simplified object graph for initial XYZPerson creation

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

    当用户修改了一个person的first name时,对象图如图3-5.

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

    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.

    badge display 任然对@"john"有一个强引用,尽管XYZPerson对象目前已经有了一个不同的firstName。 这就说明@"John"对象还存在于内存,badge视图用它来打印出firstName.

    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.

    当用户再次点击Update按钮时,badge视图告诉内部特性去匹配person对象,所以对象图像图3-6.

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

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

    这时,原来的@"John"对象已经没有任何对它的强引用了,因此它被从内存中移除了。

    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.

    默认情况下, Objective-C 特性和变量都对它们的对象保留着强引用。 很多时候这样做很好,但是它也确实会导致一个强引用循环的潜在问题。

    Avoid Strong Reference Cycles

    1、避免强引用循环

    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-way)关系时工作的很出色,但是当它们跟多组互相联系的对象一起工作时应该很小心。 如果一组对象由一个强引用循环互相连接,它们就会一直保持活跃,即使在对象组外面已经没有任何强引用存在。

    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.

    一种常见的情况是表格视图有一个对它的委托的引用,它的委托也有一个对它的引用,如图3-7.

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

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

    一旦其它对象放弃对表格视图和委托的强引用,问题就发生了。如图3-8.

    Figure 3-8  A strong reference cycle

    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.

    尽管已经没有必要在让它们在内存,因为已经没有任何对表格视图和其委托的强引用关系,除了它们自己之间的强引用关系。但是它们之间的强引用让这两个对象保持活跃。这就是所谓的一个强引用循环

    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.

    如果表格视图把它指向委托的引用改为弱引用(UITableView 和 NSTableView之间就是这么解决这个问题的),初始对象图如图3-9.

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

    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.

    这次当其它对象放弃对表格视图和委托的强引用时,就已经不存在对委托的强引用了。如图3-10.

    Figure 3-10  Avoiding a strong reference cycle

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

    这就说明委托对象将被释放,因此指向表格视图的强引用也被释放,如图3-11.

    Figure 3-11  Deallocating the delegate

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

    一旦委托被释放,表格视图也不存在任何强引用,因此它也被释放。

    Use Strong and Weak Declarations to Manage Ownership

    2. 使用强声明和弱声明来管理所有关系

    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:

    合成实例变量默认使用强引用。要想声明一个弱引用,就给特性添加一个weak属性,如下:

    @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。 不需要明确指定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 lastModificationDateproperty 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.

    在本例中,本地变量originalDate 有一个指向初始lastModificationDate对象的强引用。 当lastModificationDate 特性发生改变时,特性不再有对初始date的强引用, 但是该初始date还是在内存中,因为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例子中使用一个弱变量:

        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.

    originalDate变量有可能被nil。 当self.lastModificationDate被重新分配时,特性不再有指向原先date的强引用。 如果没有其它指向它的强引用,原先的date将被释放,并且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.

    在该例子中,新分配的对象没有设置强引用,所以它立即被释放并被设置为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 ascachedObject is still in scope (and hasn’t been reassigned another value).

    在该例子中,cachedObject变量有一个指向原始弱特性值的强引用,这样只要cachedObject还在作用域(并没有被重新分配别的值),它就不会被释放。

    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:

    时刻提醒自己在使用一个弱特性之前需要确保它不是nil。类似以下测试方法还不够:

        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:

    因为在一个多线程应用程序中,弱特性或许在测试盒方法调用的中间就被释放,即使渲染(rendering)测试也没有用。 你需要声明一个强本地变量来缓存该弱特性。请看下面代码:

        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 andsomeWeakProperty will be set to nil.

    在该方法中,第一行创建了一个强引用,保证该对象在测试和方法调用期间都将保持活跃。 在第五行时把cachedObject设置为nil, 放弃它的强引用。 如果这时初始对象(original)对象没有其它指向它的强引用,它将被释放并被设置为nil.

    Use Unsafe Unretained References for Some Classes

    3. 对一些类使用不安全并不保留的引用

    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 NSTextViewNSFont and NSColorSpace; for the full list, see Transitioning to ARC Release Notes.

    Cocoa 和 Cocoa Touch中有一小部分类还不支持弱引用,这就说明你不能声明一个弱特性或弱本地变量来跟踪它们。 这些类包括NSTextView, NSFont 和NSColorSpace等,完整列表请看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:

    如果你想对这些类使用类引用,你必须使用一个不安全索引。 对于特性,这意味着使用不安全并不保留(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.

    一个不安全引用跟一个弱引用很类似,它也不能让相关对象保持活跃,但是当它被目标对象释放时,它的值不会被设置为nil。这就意味着你将留下一个空闲指针指向现在已经被释放的对象之前所占用的内存空间,因此它是不安全的。 给一个空闲指针(dangling pointer)发送一个信息将导致程序崩溃。

    Copy Properties Maintain Their Own Copies

    4. 复制特性值以保留它们自己的备份

    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类的类接口(class interface)声明如下:

    @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视图的其中一个特性(请看下方代码),将会发生什么? 

        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视图以为它自己正在处理一个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:

    在这里,尽管那时最初给dadge视图的firstName特性值是"John", 但是现在firstName的值已经变成“Johnny"了,因为这个可变字符串已经被更改了。你或许选择badge 视图应该保留它给自己的firstName 和 lastName特性的任何备份,这样当特性们被设置了之后,它就能有效的捕捉字符串。

    @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:

    视图现在保留了它们自己对两个字符串的备份(用copy属性)。  不管设置一个可变字符串还是以后更改这个字符串,badge视图都能在同时捕捉到它的值。比如:

        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.

    这次,badge 视图拥有的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,也就是说它应该遵守NSCopying协议。

    协议在 Protocols Define Messaging Contracts中讲述。 关于NSCopying的更多信息,请看NSCopying 或者 Advanced Memory Management Programming Guide.

    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:

    如果你想直接设置一个带有copy属性的特性的实例变量,比如在一个初始化方法,不要忘记只需把初始对象的备份设置给它:

    - (id)initWithSomeOriginalString:(NSString *)aString {
        self = [super init];
        if (self) {
            _instanceVariableForCopyProperty = [aString copy];
        }
        return self;
    }

    Exercises

    1. Modify the sayHello method from the XYZPerson class to log a greeting using the person’s first name and last name.

    2. Declare and implement a new designated initializer used to create an XYZPerson using a specified first name, last name and date of birth, along with a suitable class factory method.

      Don’t forget to override init to call the designated initializer.

    3. Test what happens if you set a mutable string as the person’s first name, then mutate that string before calling your modified sayHello method. Change the NSString property declarations by adding the copy attribute and test again.

    4. Try creating XYZPerson objects using a variety of strong and weak variables in the main() function. Verify that the strong variables keep the XYZPerson objects alive at least as long as you expect.

      In order to help verify when an XYZPerson object is deallocated, you might want to tie into the object lifecycle by providing a dealloc method in the XYZPerson implementation. This method is called automatically when an Objective-C object is deallocated from memory, and is normally used to release any memory you allocated manually, such as through the C malloc() function, as described in Advanced Memory Management Programming Guide.

      For the purposes of this exercise, override the dealloc method in XYZPerson to log a message, like this:

      - (void)dealloc {
          NSLog(@"XYZPerson is being deallocated");
      }

      Try setting each XYZPerson pointer variable to nil to verify that the objects are deallocated when you expect them to be.

      Note: The Xcode project template for a Command Line Tool use an @autoreleasepool { } block inside the main() function. In order to use the Automatic Retain Count feature of the compiler to handle memory management for you, it’s important that any code you write in main() goes inside this autorelease pool block.

      Autorelease pools are outside the scope of this document, but are covered in detail in Advanced Memory Management Programming Guide.

      When you’re writing a Cocoa or Cocoa Touch application rather than a command line tool, you won’t usually need to worry about creating your own autorelease pools, because you’re tying into a framework of objects that will ensure one is already in place.

    5. Modify the XYZPerson class description so that you can keep track of a spouse or partner.

      You’ll need to decide how best to model the relationship, thinking carefully about object graph management.

  • 相关阅读:
    swift开发之--可选类型说明,类型判断 is 与类型转换 as
    壹佰文章最全总结| 《关于ASP.NETCore的分享之路》
    Blog.Core 项目已完成升级.NET5.0
    BCVP,想真正为社区做努力的开发者们
    centos7 安装 certbot 动态更新Let's encrypt 证书实现nginx免费ssl证书
    CentOS 7.5 安装 CDH6.3.2
    cloud-init在centos中关于静态ip和hostname的配置
    wait为什么要在同步块中使用? 为什么sleep就不用再同步块中?
    Java字节码技术 static、final、volatile、synchronized关键字的字节码体现 转
    说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景 转
  • 原文地址:https://www.cnblogs.com/patientAndPersist/p/4415757.html
Copyright © 2020-2023  润新知