一. 存取方法
- 在设置完属性后,编译器会自动写出一套存取方法,用于访问相应名称的变量:
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
// Same as:
@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
访问属性,可以使用点语法。编译器会把点语法转换为对存取方法的调用
区分“通过属性访问”与“直接访问”
// 通过属性访问:
self.firstName
// 直接访问:
_firstName
- 在对象内部访问实例变量应该怎么做?
(1) 在写入实例变量时,通过其“set方法”来做
self.firstName = @"aaa";
self.lastName = @"bbb";
(2) 在读取实例变量时,则直接访问它
NSLog(@"全名是 = %@", [NSString stringWithFormat:@"%@, %@", _firstName, _lastName]);
// set方法
aPerson.firstName = @"Bob"; // Same as:
[aPerson setFirstName:@"Bob"];
// get方法
NSString *lastName = aPerson.lastName; // Same as:
NSString *lastName = [aPerson lastName];
3.具体使用时需要注意的
(1) 不要在setter方法中调用setter方法
- (void)setName:(NSString *)name {
_name = [name copy]; // 正确写法
self.name = name; // 会报错
}
(2) 不要在init和dealloc方法中调用setter/getter方法
- (instancetype)init {
self = [super init];
if (self) {
self.name = @""; // 会报错
_name = @""; // 正确写法
}
return self;
}
(3) 在惰性初始化中必须通过getter方法访问属性
- (NSNumber *)gameCount {
if (!_gameCount) {
_gameCount = @13;
}
return _gameCount;
}
NSLog(@"玩游戏的次数 = %d", [_gameCount integerValue]); // nil ,错误写法,属行未初始化
NSLog(@"玩游戏的次数 = %d", [self.gameCount integerValue]); // 13 ,正确写法
- @synthesize
然而属性还有更多优势。如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编译器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名称前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为_firstName 与 _lastName。也可以在类中实现代码里通过@ synthesize语法来指定实例变量的名字:
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
前述语法会将生成的实例变量命名为 _myFirstName 与 _myLastName,而不再使用默认的名字 _firstName 与 _lastName。一般情况下无需修改默认的实例变量名,但是如果你不喜欢以下划线来命名实例变量,那么可以用这个方法将其改为自己想要的名字。
若不想令编译器自动合成存取方法,则可以自己实现。如果你只实现了其中一个存取方法,那么另外一个还是会由编译器来合成。还有一种方法能阻止编译器自动合成存取方法,就是使用@dynamic关键字,他会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。而且在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会报错,它相信这些方法能在运行期找到。比方说:从CoreData框架中的NSManagedObject类里继承了一个子类,那么就需要在运行期动态创建存取方法。继承NSManagedObject时之所以要这样做,是因为子类的某些属性不是实例变量,其数据来自后端的数据库中。例如:
@interface EOCPerson : NSManagedObject
@property NSString *firstName;
@property NSString *lastName;
@end
@implementation EOCPerson
@dynamic firstName, lastName;
@end
编译器不会为上面这个类自动合成存取方法或实例变量。如果用代码访问其中的属性,编译器也不会发出警示信息。
二. 属性特质
定义属性的时候,通常会赋予它一些特性,来满足一些对类保存数据所要遵循的需求。
@property (nonatomic, readwrite, copy) NSString *firstName;
原子性:
- nonatomic:不使用同步锁
- atomic:加同步锁,确保其原子性
读写
- readwrite:同时存在存取方法
- readonly:只有获取方法
内存管理
- assign:纯量类型(scalar type)的简单赋值操作
- strong:拥有关系保留新值,释放旧值,再设置新值
- weak:非拥有关系(nonowning relationship),属性所指的对象遭到摧毁时,属性也会清空
- unsafe_unretained :类似assign,适用于对象类型,非拥有关系,属性所指的对象遭到摧毁时,属性不会清空。
- copy:不保留新值,而是将其拷贝
注意:遵循属性定义
如果属性定义为copy,那么在非设置方法里设定属性的时候,也要遵循copy的语义
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return self;
}
要点:
- 可以用@property语法来定义对象中所封装的数据。
- 通过"特质"来指定存储数据所需要的正确语义。
- 在设置属性所对应的实例变量时,一定要遵循该属性所声明的语义。
- 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能。