合成存取方法
前面我们介绍了为成员变量自己实现 setter 方法和 getter 方法,但如果一个了类中有很多成员变量时,会非常的不便。
objective-c从 OC 2.0版本开始,自动合成了setter 方法和 getter 方法。而且,如果开发者需要自己控制某个setter 方法和 getter 方法的实现时,可以自己提供 setter 方法和 getter 方法,开发者提供的setter 方法和 getter 方法会覆盖系统自动合成的相应方法。
使系统自动合成 setter 和 getter 的方法的步骤
- 在类接口部分使用@property 指令定义属性。——@property 指令放在属性定义的前面,直接放在@interface,@end 之间定义。
- 在类实现的部分使用@synthesize 指令声明该属性。
当采用上面的2步后,会合成成对的 setter 和 getter 方法。还会自动在类的实现部分定义一个与 getter 方法同名的成员变量。这部分可以参见之前的一篇博文《【IOS 开发学习总结-OC-11】★objective-c面向对象之——封装和访问控制符 》
如果为某个类定义了一个成员变量,并提供了相应的setter 和 getter 方法,那么可以称为定义了一个属性(property)。
Xcode4.0后的编码规范推荐将成员变量名定义为以下划线开头,比如在 Xcode 的模板代码中有这样的代码:@synthesize window=_window;
该代码的作用是:告诉系统合成的property对应的成员变量为_window, 而不是 window。
@synthesize的语法格式:@synthesize property名 =成员变量名;
或者是@synthesize property名
;
@synthesize 后如果没有有指定成员变量名,成员变量默认与合成的 getter 方法重名。
示范合成存取方法的用法。示例代码:
FKUser.h文件:
#import <Foundation/Foundation.h>
@interface FKUser : NSObject
// 使用@property定义3个property
@property (nonatomic) NSString* name;
@property NSString* pass;
@property NSDate* birth;
@end
FKUser.m文件:
#import "FKUser.h"
@implementation FKUser
// 为3个property合成setter和getter方法,
// 指定name property底层对应的成员变量名为_name
@synthesize name = _name;
@synthesize pass;
@synthesize birth;
// 实现自定义的setName:方法,添加自己的控制逻辑
- (void) setName:(NSString*) name
{
self->_name = [NSString stringWithFormat:@"+++%@"
, name];
}
@end
FKUserTest.m文件:
#import "FKUser.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建FKUser对象
FKUser* user = [[FKUser alloc] init];
// 调用setter方法修改user成员变量的值
[user setName:@"admin"];
[user setPass:@"1234"];
[user setBirth:[NSDate date]];
// 访问user成员变量的值
NSLog(@"管理员账号为:%@,密码为:%@,生日为:%@"
, [user name] , [user pass] , [user birth]);
}
}
程序通过@property,@synthesize 合成 setter,getter 方法后,程序就可以在后面通过setter,getter 方法存取成员变量的值。
定义@property时用到的特殊指示符
assign
assign:指定对属性只是简单赋值,不更改对所赋的值的引用计数。主要适用于:NSInteger 等基本类型,以及 short,float,结构体等各种 C 数据类型。那么问题来了,
为什么NSInteger 等基本类型,以及 short,float,结构体等各种 C 数据类型不存在回收的问题呢?
iOS中的垃圾处理机制是根据一个对象的索引数(引用计数)来处理的,为0的时候表示没有地方使用该对象,则该对象将被清除,而基本数据类型不属于对象,它的创建和使用都是在内存中,超出对应方法体即被清除,所以不需要使用垃圾处理机制,无需记录索引值(计数),不存在回收的问题。所以使用assgin。
atomic与nonatomic
atomic(nonatomic):指定合成的存取方法是否为原子操作。默认为atomic。
如果用atomic,那么合成的存取方法都是线程安全的——当一个线程进入存,取方法的方法体后,其他线程无法进入该存,取方法,这样可以避免多线程破坏对象的数据完整性。
copy
copy:使用 copy 指示符时,当调用 setter 方法对成员变量赋值时,会将被赋值的对象复制一个副本,再将副本赋值给成员变量。copy 会将原成员变量所引用对象的引用计数减1。
何时考虑使用 copy?
当成员变量的类型是可变类型或其子类是可变类型时,被赋值的对象可能在赋值之后被修改,如果程序不需要这种修改影响 setter 方法设置的成员变量的值,此时考虑用 copy 指示符。
示例程序:
FKBook.h
#import <Foundation/Foundation.h>
@interface FKBook : NSObject
// 使用@property定义1个property
@property (nonatomic) NSString* name;
@end
FKBook.m
#import "FKBook.h"
@implementation FKBook
@synthesize name;
@end
FKBookTest.m
#import "FKBook.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKBook* book = [[FKBook alloc] init];
//NSMutableString是NSString的子类
NSMutableString* str = [NSMutableString
stringWithString:@"iOS讲义"];
// 对book的name属性赋值
[book setName:str];
// 输出book的name属性
NSLog(@"book的name为:%@" , [book name]);
// 修改str字符串
[str appendString:@"是一本很好的iOS学习图书"];
// 下面代码将会看到book的name属性也会被修改
NSLog(@"book的name为:%@" , [book name]);
}
}
编译运行该程序后,输出内容为:
book的name为:iOS讲义
book的name为:iOS讲义是一本很好的iOS学习图书
如果我们不想 FKBook 的 name 属性不会随着NSMutableString对象的改变而改变,我们可以在定义 name 属性的一行增加copy 指示符就可以了, @property (nonatomic,copy) NSString* name;
setter&getter
用于为合成的setter,getter方法指定自定义的方法名。
举个栗子:getter=abc
指定 getter 方法名为 abc,setter=xyz:
,则指定 setter 方法名为 xyz。注意:——setter方法不要忘了带冒号,因为它要带参数。
示例程序:
FKItem.h
#import <Foundation/Foundation.h>
@interface FKItem : NSObject
// 使用@property定义1个property,并指定自定义的getter、setter方法名
@property (assign , nonatomic , getter=wawa , setter=nana:) int price;
@end
FKItem.m
#import "FKItem.h"
@implementation FKItem
@synthesize price;
@end
FKItemTest.m
#import "FKItem.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKItem* item = [[FKItem alloc] init];
// 设置price属性
[item nana:30];
// 访问price属性
NSLog(@"item的price为:%d" , [item wawa]);
}
}
这里的的 setPrice: 方法和 获取的price 方法已经不存在,取而代之的是 nana 和 wawa。
readonly&readwrite
readonly指示系统只合成 getter 方法,不再合成 setter 方法。readwrite是默认值,2个方法都合成。
retain
使用retain指示符定义属性时,当把某个对象赋值给该属性时,该属性原来所引用的对象的引用计数-1,被赋值对象的引用计数+1.
说明:在启动 ARC 的情况下,一般少用 retain。但在未启用 ARC 的情况下,retain是很有用的。
示例程序:(下面的程序关闭了 ARC)
FKWin.h
#import <Foundation/Foundation.h>
@interface FKWin : NSObject
// 使用@property定义1个property
@property (nonatomic , retain) NSDate* date;
@end
FKWin.m
#import "FKWin.h"
@implementation FKWin
@synthesize date;
@end
FKWinTest.m
#import "FKWin.h"
int main(int argc , char * argv[])
{
FKWin* win = [[FKWin alloc] init];
NSDate* date = [[NSDate alloc] init];
// 第一次赋值时,date的引用计数为1
NSLog(@"date的引用计数为:%ld" , date.retainCount);
// 由于使用了retain指示符,赋值时导致date的引用计数+1
[win setDate:date];
// 下面输出的引用计数为2
NSLog(@"[win date]的引用计数为:%ld" , [win date].retainCount);
// 释放date的引用计数,date的引用计数-1
[date release];
// 下面输出的引用计数为1
NSLog(@"[win date]的引用计数为:%ld" , [win date].retainCount);
}
点语法
objective-c 允许使用简化的点语法访问属性和对属性赋值。其本质仍是调用 getter,setter 方法。
示例程序:
FKCard.h
#import <Foundation/Foundation.h>
@interface FKCard : NSObject
// 使用@property定义2个property
@property (nonatomic , copy) NSString* flower;
@property (nonatomic , copy) NSString* value;
@end
FKCard.m
#import "FKCard.h"
@implementation FKCard
@synthesize flower;
@synthesize value;
@end
FKCardTest.m
#import "FKCard.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
FKCard* card = [[FKCard alloc] init];
// 通过点语法对属性赋值
card.flower = @"♠";
card.value = @"A";
// 通过点语法来访问属性值
NSLog(@"我的扑克牌为:%@%@", card.flower, card.value);
}
}