Skip to content

Files

Latest commit

03035da · Jan 9, 2022

History

History
173 lines (110 loc) · 7.01 KB

03.md

File metadata and controls

173 lines (110 loc) · 7.01 KB

三、属性

既然我们已经探索了哪些数据类型是可用的,我们就可以讨论如何以高效的方式实际使用它们。我们在 Hello,Objective-C 中学习了如何声明属性,但是这一章深入探讨了公共属性和实例变量背后的细微差别。首先,我们将快速了解属性和实例变量的基本语法,然后讨论如何使用行为属性来修改访问器方法。

申报财产 x

可以使用@property指令在接口中声明属性。作为快速回顾,让我们看一下我们在你好,客观-C 一章中创建的Person.h文件:

    #import <Foundation/Foundation.h>

    @interface Person : NSObject

    @property (copy ) NSString *name;

    @end

这声明了类型为NSString的名为name的属性。(copy)属性告诉运行时当有人试图设置name的值时该做什么。在这种情况下,它会创建该值的独立副本,而不是指向现有对象。我们将在下一章内存管理中详细讨论这一点。

实现属性

你好,Objective-C 中,我们使用了@synthesize指令来自动创建 getter 和 setter 方法。请记住,getter 方法只是属性的名称,默认的 setter 方法是setName:

    #import "Person.h"

    @implementation Person

    @synthesize name = _name;

    @end

但是,也可以手动创建访问器方法。手动这样做有助于理解@property@synthesize在幕后做什么。

附带代码示例:手动属性

首先,在Person界面添加一个新属性:

    @property (copy) NSString *name;
    @property unsigned int age;

请注意,我们将age存储为原始数据类型(而不是指向对象的指针),因此它不需要在属性名称前加星号。回到Person.m,明确定义访问器方法:

    - (unsigned int )age {
        return _age;
    }

    - (void )setAge:(unsigned int )age {
        _age = age;
    }

这正是@synthesize会为我们做的,但是现在我们有机会在赋值之前验证值。然而,我们错过了一件事:_age实例变量。@synthesize自动创建了一个_name ivar,允许我们为了name房产放弃这个。

实例变量

实例变量,也称为 ivars,是用于类内部的变量。它们可以在@interface@implementation指令之后在花括号内声明。例如,在Person.h中,将接口声明更改为以下内容:

    @interface Person {
        unsigned int _age;
    }

这定义了一个名为_age的实例变量,所以这个类现在应该编译成功了。默认情况下,接口中声明的实例变量受保护。等效的 C#类定义类似于:

    class Person {
        protected uint _age;
    }

Objective-C 作用域修饰符与 C#中的相同:私有变量只可由包含类访问,受保护变量可由所有子类访问,公共变量可由其他对象访问。您可以在@interface中使用@private@protected@public指令定义实例变量的范围,如以下代码所示:

    @interface Person : NSObject {
      @private
        NSString *_ssn;
      @protected
        unsigned int _age;
      @public
        NSString *job;
    }

公共 ivars 实际上有点超出客观-C 标准。带有公共变量的类更像一个 C 结构,而不是类;您需要使用->指针操作符,而不是通常的消息传递语法。例如:

    Person *frank = [[Person alloc] init];
    frank->job = @"Astronaut";
    NSLog (@"%@", frank->job );
    // NOT: [frank job];

然而,在大多数情况下,您会希望通过使用@property声明而不是公共实例变量来隐藏实现细节。此外,由于实例变量在技术上是实现细节,许多程序员喜欢将所有实例变量保密。考虑到这一点,在@implementation中声明的 ivars 默认是私有的。因此,如果您将_age声明移到Person.m而不是标题:

    @implementation Person {
        unsigned int _age;
    }

_age将作为一个私有变量。在子类中使用实例变量时,请记住这一点,因为接口和实现声明的不同默认值可能会让新加入 Objective-C 的人感到困惑

自定义访问者

但是关于实例变量就足够了;让我们回到属性。可以使用几个属性声明属性(例如,(copy))自定义访问器方法。一些最重要的属性是:

  • getter=getterName–自定义 getter 访问器方法的名称。请记住,默认值只是属性的名称。
  • setter=setterName–自定义 setter 访问器方法的名称。请记住,默认值是set后跟物业名称(例如:setName)。
  • readonly–将属性设为只读,这意味着将只合成一个 getter。默认情况下,属性是读写的。这不能与setter属性一起使用。
  • nonatomic–表示访问器方法不需要线程安全。默认情况下,属性是原子的,这意味着 Objective-C 将使用锁/retain(在下一章中描述)从 getter/setter 返回完整的值。然而,请注意,这并不能保证线程间的数据完整性——仅仅是获取器和设置器是原子的。如果您不在线程环境中,非原子属性会快得多。

*自定义 getter 名称的一个常见用例是布尔命名约定。许多程序员喜欢在布尔变量名前面加上is。这很容易通过getter属性实现:

    @property (getter=isEmployed) BOOL employed;

在内部,类可以使用employed变量,但是其他对象可以使用isEmployedsetEmployed访问器与对象交互:

    Person *frank = [[Person alloc] init];
    [frank setName:@"Frank" ];
    [frank setEmployed:YES ];
    if ([frank isEmployed ]) {
        NSLog (@"Frank is employed" );
    } else {
        NSLog (@"Frank is unemployed" );
    }

许多其他属性与内存管理有关,这将在下一节中讨论。也可以通过用逗号分隔多个属性来将它们应用于单个属性:

    @property (getter=isEmployed, readonly) BOOL employed;

点语法

除了 getter/setter 方法,还可以使用点符号来访问声明的属性。对于 C#开发人员来说,这应该比 Objective-C 的方括号消息语法更熟悉:

    Person *frank = [[Person alloc] init];
    frank.name = @"Frank";    // Same as [frank setName:@"Frank"];
    NSLog(@"%@", frank.name); // Same as [frank name];

注意,这只是一个方便——它直接转换为前面描述的 getter/setter 方法。点符号不能用于实例方法。

总结

属性是任何面向对象编程语言不可或缺的一部分。它们是方法操作的数据。@property指令是一种配置属性行为的便捷方式,但是它不能做任何不能通过手动创建 getter 和 setter 方法来完成的事情。

在下一章中,我们将详细了解属性是如何存储在内存中的,以及一些用于控制这种行为的新属性。之后,我们将深入研究方法,它完善了 Objective-C 的核心面向对象工具*