@property和@synthesize
Synthesized property 'xX' must either be named the same as a compatible ivar or must explicitly name an ivar
在 64-bit时,运行时系统会自动给类添加 ivar,添加的 ivar 以一个下划线"_"做前缀。
上面声明部分的 @synthesize window=_window; 意思是说,window 属性为 _window 实例变量合成访问器方法。
也就是说,window属性生成存取方法是setWindow,这个setWindow方法就是_window变量的存取方法,它操作的就是_window这个变量。通过这个看似是赋值的这样一个操作,我们可以在@synthesize 中定义与变量名不相同的getter和setter的命名,籍此来保护变量不会被不恰当的访问。
下面是一个常见的例子
写法一:
@interface MyClass:NSObject{
MyObjecct *_myObject;
}
@property(nonamtic, retain) MyObjecct *myObject;
@end
@implementatin MyClass
@synthesize myObject=_myObject;
@interface MyClass:NSObject{
}
@property(nonamtic, retain) MyObjecct *myObject;
@end
@implementatin MyClass
@synthesize myObject=_myObject;
self.nameVarPtr = [[ObjectName alloc] init]
nameVarPtr = [[ObjectName alloc] init]
self.nameVarPtr=xxx 这种赋值方式等价于调用 [self setnameVarPtr:xxx], 而setnameVarPtr:xxx的方法的实现又是依赖于@property的属性的,比如retain,assign等属性。
nameVarPtr = xxx 的赋值方式,仅仅是对一个指针进行赋值。nameVarPtr仅仅是一个指针变量,记录了xxx的地址。在这个过程中不会调用setter方法,不会调用setter方法,就和@property没有关系,从而,也和retain,assign等属性没有关系。这种赋值方式就是一个简单的指针赋值。
综上,对成员变量进行赋值,为防内存泄露需要注意的点:
1.self调用setter方法的方式
ObjectName* tmp= [[ObjectName alloc] init];
self.nameVarPtr =tmp; //retainCount=2
[tmp release]; //retainCount=1
2.指针赋值方式,不会调用setter方法
nameVarPtr= [[ObjectName alloc] init]; // retainCount=1
所以,笔者建议大家在对某个变量进行赋值操作的时候,尽量要写self.myObj = xxx; 这才是最可靠的方法。
@property和@synthesize可以自动生成某个类成员变量的存取方法
readwrite:这个属性是默认的情况,会自动为你生成存取器
assign:一般用来处理基础类型,比如int、float等等,如果你声明的属性是基础类型的话,assign是默认的,你可以不加这个属性
nonatomic:默认是有该属性的,这个属性是为了保证程序在多线程情况,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题
readonly:只生成getter不会有setter方法
copy:这个会自动生成你赋值对象的克隆,相当于在内存中新生成了该对象的副本,这样一来,改变赋值对象就不会改变你声明的这个成员变量了
retain:会自动retain赋值对象
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率
assign:指定setter方法用简单的赋值,这是默认操作。你可以对标量类型(如int)使用这个属性。你可以想象一个float,它不是一个对象,所以它不能retain、copy。
assign:简单赋值,不更改索引计数(Reference Counting).使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等)
retain:指定retain应该在后面的对象上调用,前一个值发送一条release消息。你可以想象一个NSString实例,它是一个对象,而且你可能想要retain它。
retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 ,使用retain: 对其他NSObject和其子类 ,retain,是说明该属性在赋值的时候,先release之前的值,然后再赋新值给属性,引用再加1。
copy:指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。copy:建立一个索引计数为1的对象,然后释放旧对象,copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。
readonly:将只生成getter方法而不生成setter方法(getter方法没有get前缀)
。
readwrite:默认属性,将生成不带额外参数的getter和setter方法(setter方法只有一个参数)
。
nonatomic:对于对象的默认属性,就是setter/getter生成的方法是一个原子操作。如果有多个线程同时调用setter的话,不会出现某一个线程执行setter全部语句之前,另一个线程开始执行setter的情况,相关于方法头尾加了锁一样。
nonatomic:不保证setter/getter的原子性,多线程情况下数据可能会有问题。nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。先释放原先变量,再将新变量 retain然后赋值;
注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。
前置下划线是一种为了帮助区分实例变量和访问方法的约定。对于编译器来说它只是一种变量重命名而已。
考虑以下代码的区别(不使用ARC的情况下):
self.date = [NSDate date]; // 正确,set方法首先释放原来的值
date = [NSDate date]; // 错误,省略set方法将导致内存泄露
_date = [NSDate date]; // 错误,但是这样很容易看出来它不是一个局部变量
使用ARC的情况下变量将不会内存泄露,但是省略 @property 标志却是错误的:
@property (copy) string;
// ...
self.string = someString; // 正确,string将被复制
string = someString; // 错误,string被保留而不是复制
_string = someString; // 错误,但是这样更容易区别
最坏的情况,Core Data等API是基于KVC消息来实现延迟加载的, 如果你不小心忽略了访问方法,数据将被返回为nil
以下就是为什么使用@synthesize var=_var
的原因
self.var
通过访问方法的引用(包括set和get方法)_var
直接引用(不通过set或者get方法)var
无效的引用
考虑到当
是LLVM 4.0自动生成的你可以把它当做Objective-C的默认命名规则。@synthesize
被省略的时候@synthesize var=_var
更详细的请继续阅读...
Modern runtime
在Objective-C 2.0中你可以这样声明变量:
@interface User : NSObject
@property (nonatomic, assign) NSInteger age;
@end
@implementation User {
@synthesize age; //LLVM 4.0之后这一行可以省略
@end
将被编译器解释为:
@interface User : NSObject {
NSInteger age;
}
@end
@implementation User
-(void)setAge:(NSInteger)newAge {
age=newAge;
}
-(void)age {
return age;
}
@end
如果你更愿意使用前置下划线规则的话,向下面这样:
@synthesize age=_age;
因为有modern runtime,你要做的就这么多。如果你不提供实例变量,编译器会帮你添加一个。下面是将被编译的代码:
@interface User : NSObject {
NSInteger _age;
}
@end
@implementation User
-(void)setAge:(NSInteger)newAge {
_age=newAge;
}
-(void)age {
return _age;
}
@end
如果你同时添加了ivar和@property将会怎么样呢?如果这个变量有相同的变量名和类型,编译器将直接使用它而不是产生一个新的变量。引用自 The Objective-C Programming Language > Declared Properties >Property Implementation Directives:
由于runtime的关系,访问方法的行为存在不同:
对于modern runtime来说,实例变量在需要的时候生成。如果已经存在一个同名的实例变量,这个已经存在的变量就会被使用。
对于legacy runtime来说,实例变量必须已经在当前类的声明中声明过。如果一个同名实例变量作为属性存在并且它的类型和属性的类型兼容,这个同名实例变量将被使用,否则将发生编译错误。
Legacy runtime
如果要支持legacy runtime,必须提供一个同名并且和属性类型兼容的实例变量或者在@synthesize语句里面指定另外一个已经存在的实例变量。
没用下划线的legacy代码:
@interface User : NSObject {
NSInteger age;
}
@property (nonatomic, assign) NSInteger age;
@end
@implementation User
@synthesize age;
@end
使用下划线规则:
@interface User : NSObject {
NSInteger _age;
}
@property (nonatomic, assign) NSInteger age;
@end
@implementation User
@synthesize age = _age;
@end
What is the best way?
苹果公司不提倡方法使用下划线,但是对于变量,苹果公司是提倡使用下划线的。
苹果关于方法的代码规范: Coding Guidelines for Cocoa: Typographic Conventions:
避免使用前置下划线符号区表示私有,尤其是方法。苹果保留了这条约定。第三方使用将引起命名空间冲突,他们可能不小心覆盖一个他们自己的私有方法,这将导致灾难性的后果。
苹果关于变量的代码规范:Declared Properties and Instance Variables
保证实例变量名明确描述了变量的属性。通常情况下,你不应该直接访问变量而是应该通过访问方法(在init和dealloc方法中,你直接访问变量)。为了帮助标明这一点,在实例变量前加上下划线, 例如:
@implementation MyClass { BOOL _showsTitle; }
ISO/IEC 9899 7.1.3 Reserved identifiers (aka C99):
- 所有以下划线开始接一个大写字母或者另一个下划线的标示符将被保留。
- 所有以下划线开始的标示符将被保留用作文件作用域标示符无论是普通还是标记命名空间。
最重要的是,双下划线开头在传统上保留用作预处理/编译器/库。这防止你在代码中使用__block
,苹果将此引入作为一个新的非标准关键字。
Google Objective-C Style guide:
变量名小写字母开头,混合使用大小写来区分单词。类成员变量加后置下划线。例如: myLocalVariable, myInstanceVariable_. 类成员用作KVO/KVC绑定的可以使用前置下划线当且仅当它无法使用Objective-C 2.0 的 @property
Google的后置下划线并没有使你在Xcode开始自动完成之前多输入一个字符,但是如果下划线写在末尾,你将慢慢发现它原来是一个实例变量。
C++和Core Data属性不提倡使用前置下划线(参见What are the rules about using an underscore in a C++ identifier?)(尝试在model中添加一个前置下划线你将看到"Name must begin with a letter").
无论你怎么选择都不太可能发生冲突,当发生冲突的时候编译器会给出警告。当你犹豫不决的时候,使用默认的LLVM规范:@synthesize var=_var;