如下:「attribute(s)」,「特性」是指同一事物(都指@property
后面括号内的单词)。html
用Objective-C作过开发的朋友都知道,类里面的属性(能够近似地理解为类的变量)是用@property
关键字定义的,而后@property
后面的括号,会写上若干「特性(attribute)」,后面跟数据类型、属性名称。如:程序员
@property (copy, nonatomic) NSString *name;
复制代码
要搞清楚「特性」,先搞清楚@property
,为何要有@property
?objective-c
在2006年的WWDC大会上,苹果发布了Objective-C 2.0,其中就包括Properties这个新的语法,把原来的实例变量定义成Properties(属性)。这个变化,和之前相比,有什么变化呢?缓存
没有Properties以前,定义实例变量,是这样的:安全
@interface Person : NSObject {
@public
NSString *name;
@private
int age;
}
@end
复制代码
而后在.h文件,声明setter和getter方法(setter和getter统称「accessors/存取器/访问器」),再在.m文件实现setter和getter,这样就能够封装起来,供其余类访问(取值、赋值)了。bash
然而,即便不使用setter和getter,其余类也能够经过->
来直接访问,如:app
personA->name = @"123";
NSLog(@"personA->name:%@", personA->name);
复制代码
为何要getter和setterless
那么,为何还要如此麻烦地声明和实现setter和getter呢?主要基于三个缘由(参考:Please explain Getter and Setters in Objective C):性能
所以,写getter和setter,可算是Objective-C中「约定俗成」的作法了。(Swift有相似的「Computed Properties/计算属性」)ui
因此,在没有Objective-C2.0的@property
以前,咱们几乎须要为全部的实例变量,手动写getter和setter——听听就以为很可怕,对不对?
庆幸的是,程序员都喜欢「偷懒」,因此就有了2006年Objective-C2.0中的新语法:Properties。
它帮咱们自动生成getter和setter(声明方法,并实现方法。固然,这部分代码并不会出如今你的项目中,是隐藏起来的)。
不过,@property
的写法,也通过数次变迁(新旧写法混在一块儿,就更让人困惑了):
@property
声明了属性——这只是帮咱们在声明了getter和setter;@synthesize
自动合成getter和setter的实现。@synthesize
会默认自动生成一个「下划线+属性名」的实例变量。好比@property (copy, nonatomic) NSString *name;
以后,就能够直接使用_name
这个变量了。@synthesize
也不须要了。一个@property
搞定。因此,如今咱们写@property
声明属性,实际上是作了三件事
这就是@property
为咱们所作的事情。
知道它为咱们作了什么,天然也就能回答:「为何要有@property
?」这个问题了。
@property (copy, nonatomic) NSString *name;
复制代码
这种写法,你们确定都写过,不过,后面跟着的这个括号又是什么玩意儿呢?
官方把括号里面的东西,叫作「attribute/特性」。
先试一下,把括号里的两个单词都删掉,你会发现,还能正常工做。而事实上,如下两种写法,是等价的:
@property () NSString *name;// 或者@property NSString *name;
@property (atomic, strong, readwrite) NSString *name;
复制代码
由于attribute主要有三种类型(实际上最多能够写6个特性,后面详述),每种类型都有默认值。若是什么都不写,系统就会取用默认值(看看,苹果良苦用心,偷偷帮咱们作了那么多事情)。
如上所述,attributes有三种类型:
比较简单的一句话理解就是:是否给setter和getter加锁(是否保证setter或者getter的每次访问是完整性的)。
原子性,有atomic和nonatomic两个值可选。默认值是atomic(也就是不写的话,默认是atomic)。
使用atomic,在必定程度上能够保证线程安全,「atomic的做用只是给getter和setter加了个锁」。也就是说,有线程在访问setter,其余线程只能等待完成后才能访问。
它能保证:即便多个线程「同时」访问这个变量,atomic会让你获得一个有意义的值(valid value)。可是不能保证你得到的是哪一个值(有多是被其余线程修改过的值,也有多是没有修改过的值)。
而用nonatomic,则不保证你得到的是有效值,若是像上面所述,读、写两个线程同时访问变量,有可能会给出一个无心义的垃圾值。
这样对比,atomic就显得比较鸡肋了,由于它并不能彻底保证程序层面的线程安全,又有额外的性能耗费(要对getter和setter进行加锁操做,我验证过,在某个小项目中将全部的nonatomic删除,内存占用平均升高1M左右)。
因此,你会见到,几乎全部状况,咱们都用nonatomic。
存取特性有readwrite(默认值)和readonly。
这个从名字看就很容易理解,定义了这个属性是「只读」,仍是「读写」皆可。
若是是readwrite,就是告诉编译器,同时生成getter和setter。若是是readonly,只生成getter。
最经常使用到strong、weak、assign、copy4个attributes。(还有一个retain,不怎么用了)
ARC新增的特性。
代表你须要引用(持有)这个对象(reference to the object),负责保持这个对象的生命周期。
注意,基本数据类型(非对象类型,如int, float, BOOL),默认值并非strong,strong只能用于对象类型。
ARC新增的特性。
也会给你一个引用(reference/pointer),指向对象。可是不会主张全部权(claim ownership)。也不会增长retain count。
若是对象A被销毁,全部指向对象A的弱引用(weak reference)(用weak修饰的属性),都会自动设置为nil。
在delegate patterns中经常使用weak解决strong reference cycles(之前叫retain cycles)问题。
为了说明copy,咱们先举个栗子:
我在某个类(class1)中声明两个字符串属性,一个用copy,一个不用:
@property (copy, nonatomic) NSString *nameCopy;
// 或者能够省略strong, 编译器默认取用strong
@property (strong, nonatomic) NSString *nameNonCopy;
复制代码
在另外一个类中,用一个NSMutableString对这两个属性赋值并打印,再修改这个NSMutableString,再打印,看看会发生什么:
Class1 *testClass1 = [[Class1 alloc] init];
NSMutableString *nameString = [NSMutableString stringWithFormat:@"Antony"];
// 用赋值NSMutableString给NSString赋值
testClass1.nameCopy = nameString;
testClass1.nameNonCopy = nameString;
NSLog(@"修改nameString前, nameCopy: %@; nameNonCopy: %@", testClass1.nameCopy, testClass1.nameNonCopy);
[nameString appendString:@".Wong"];
NSLog(@"修改nameString后, nameCopy: %@; nameNonCopy: %@", testClass1.nameCopy, testClass1.nameNonCopy);
复制代码
打印结果是:
修改nameString前, nameCopy: Antony; nameNonCopy: Antony
修改nameString后, nameCopy: Antony; nameNonCopy: Antony.Wong
复制代码
我只是修改了nameString
,为何testClass1.nameNonCopy
的值没改,它也跟着变了?
由于strong
特性,对对象进行引用计数加1,只是对指向对象的指针进行引用计数加1,这时候,nameString
和testClass1.nameNonCopy
指向的实际上是同一个对象(同一块内存),nameString
修改了值,天然影响到testClass1.nameNonCopy
。
而copy
这个特性,会在赋值前,复制一个对象,testClass1.nameCopy
指向了一个新对象,这时候nameString
怎么修改,也不关它啥事了。应用copy
特性,系统应该是在setter中进行了以下操做:
- (void)setNameCopy:(NSString *)nameCopy {
_nameCopy = [nameCopy copy];
}
复制代码
你们了解copy
的做用了吧,是为了防止属性被意外修改的。那何时要用到copy
呢?
全部有mutable(可变)版本的属性类型,如NSString, NSArray, NSDictionary等等——他们都有可变的版本类型:NSMutableString, NSMutableArray, NSMutableDictionary。这些类型在属性赋值时,右边的值有多是它们的可变版本。
扩展
若是不用copy
,而是在赋值前,调用copy方法,能够达到一样的目的:
// 这时候也能够确保nameNonCopy不会被意外修改
testClass1.nameNonCopy = [nameString copy];
复制代码
若是用copy修饰NSMutableString、NSMutableArray会发生什么?
若是用copy
修饰NSMutableString,在赋值的时候会报以下警告:
Incompatible pointer types assigning to 'NSMutableString *' from 'NSString *'
复制代码
而若是用copy
修饰NSMutableArray,则在调用addObject:时直接crash:
reason: '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x1700045c0'
复制代码
若是理解了「copy
特性,就是在setter中,进行了copy操做」,就很容易知道以上报错的缘由:属性在赋值时,调用setter,已经将本来mutable的对象,copy成了immutable的对象(NSMutableString变成NSString,NSMutableArray变成NSArray)。
是非ARC时代的特性,
它的做用和weak相似,惟一区别是:若是对象A被销毁,全部指向这个对象A的assign属性并不会自动设置为nil。这时候这些属性就变成野指针,再访问这些属性,程序就会crash。
所以,在ARC下,assign就变成用于修饰基本数据类型(Primitive Type),也就是非对象/非指针数据类型,如:int、BOOL、float等。
注意,在非ARC时代,尚未strong的时候。assign是默认值。ARC下,默认值变成strong了。这个要注意一下,不然会引发困扰。
retain是之前非ARC时代的特性,在ARC下并不经常使用。
它是strong的同义词,二者功能一致。不知道为何还保留着,这对新手也会形成必定困扰。
因此,总结一下。
其实,除了上面3种常常用到的特性类型,还有2种不太见到。
按字面意思,很容易理解,就是重命名getter和setter方法。
Transitioning to ARC Release Notes中写道:
You cannot give an accessor a name that begins with new. This in turn means that you can’t, for example, declare a property whose name begins with new unless you specify a different getter
存取方法不能以new
开头,若是你要以new
开头命名一个属性:@property (copy, nonatomic) NSString *newName;
因而会默认生成一个new开头的getter方法:
这时候就会报错:Property follows Cocoa naming convention for returning 'owned' objects
。
解决办法,就是用**getter=**重命名getter方法:
@property (copy, nonatomic, getter=theNewName) NSString *newName;
复制代码
为了更好地和Swift混编(配合Swift的optional类型),在Xcode 6.3,Objective-C新增了一个语言特性,nullability。具体就是以上4个新特性。
若是设置为null_resettable
,则要重写setter或getter其中之一,本身作判断,确保真正返回的值不是nil。不然报警告:Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil
Nullability的写法以下:
@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray *allItems;
// 也能够将nullable, nonnull, null_unspecified, null_resettable三个修饰语前面加双下划线,用于修饰指针、参数、返回值等(null_resettable只能在属性括号中使用)
@property (copy, readonly) NSArray * __nonnull allItems;
复制代码
Nullability的默认值:null_unspecified
——未指定。若是某个属性填写了Nullability特性(好比写了nonnull),没有填写Nullability的属性,会出现以下警告:
Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified)
复制代码
可是若是每一个属性都一一写上,稍嫌麻烦。而由于大多数属性是nonnull
的,因此苹果定义了两个宏,NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
(两个宏之间,叫作Audited Regions)。
将全部属性包在这两个宏中,就无需写nonnull修饰语了,只须要在「可为空」的属性里,写上nullable便可:
NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// 只须要为「不可为空」的参数、属性、返回值加上修饰语nullable便可
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;
@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
复制代码
因此!综上所述,attribute最多能够写6个进去:1.原子性、2.存取特性、3.内存管理特性、4.重命名getter、5.重命名setter,6.nullability:
@property (nonatomic, readonly, copy, getter=theNewTitle, setter=setTheNewTitle:, nullable) NSString *newTitle;
复制代码
不过,应该没有谁闲得蛋疼会这样写的。
最短的写法就是什么都不写,连括号均可以不要:
@property BOOL isOpen;
复制代码
GG