既然我们已经探索了哪些数据类型是可用的,我们就可以讨论如何以高效的方式实际使用它们。我们在 Hello,Objective-C 中学习了如何声明属性,但是这一章深入探讨了公共属性和实例变量背后的细微差别。首先,我们将快速了解属性和实例变量的基本语法,然后讨论如何使用行为属性来修改访问器方法。
可以使用@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
变量,但是其他对象可以使用isEmployed
和setEmployed
访问器与对象交互:
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 的核心面向对象工具*