52个有效方法(6) - 理解“属性”这一概念
属性
“属性”(property)是oc的一项特性,用于封装对象中的数据。
-
@property是声明属性的语法(
@property = ivar + getter + setter
)。oc对象通常会把其所需的数据保存为各种实例变量(
ivar
)。实例变量一般通过“存取方法”(accessmethod
)来访问。什么是存取方法:
getter
和setter
方法(access method = getter + setter
),其中getter
用于获取变量value
, 而setter
用于写入value
。 @property
可以快速方便的为实例变量创建存取器。
// man.h #import <foundation/foundation.h> @interface man : nsobject @property (nonatomic,strong)nsstring *name; @property (nonatomic,strong)nsstring *sex; @end
与下面的写法等效
// man.h #import <foundation/foundation.h> @interface man : nsobject { // 实例变量 nsstring *name; nsstring *sex; } // setter - (void)setname:(nsstring *)newname; // getter - (nsstring *)name; // setter - (void)setsex:(nsstring *)newsex; // getter - (nsstring *)sex; @end
-
通常使用“点语法” 来让编译器自动调用相关的存取方法(
access method = getter + setter
)。self. name = @"sky"; nsstring *name = self. name;
点语法有什么优势呢?
省时,省力 :如果使用了属性,编译器会自动编写访问属性所需的方法。这个过程由编译器在编译期执行,看不到这些
get set
源代码。编译器会自动向类中添加适当类型的实例变量,并且在属性名前添加下划线。
如果你不想让编译器自动合成存取方法,则可以自己实现。如果你只实现了其中一个存取方法,那么另一个还是会由编译器来合成。
当我们同时重写了
setter and getter
方式时,系统会报错,原因是找不到实例变量。其解决方法: 在.m
的文件中使用@synthesize
。
@synthesize
是为属性添加一个实例变量名,或者说别名。同时会为该属性生成setter/getter
方法。在
protocol
中使用property
只会生成setter
和getter
方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性。需要使用@synthesize
生成setter
和getter
。当你在子类中重载了父类中的属性,你必须 使用
@synthesize
来手动合成ivar
。-
当我们同时重写了
setter and getter
方式时,需要在.m的文件中使用@synthesize
。// man.m #import "man.h" @implementation man @synthesize name = _name; // setter - (void)setname:(nsstring *)name { _name = name; } // getter - (nsstring *)name { return _name; } @end
-
**@synthesize name = _name**
_name
是成员变量name
是属性作用是告诉编译器
name
属性为_name
实例变量生成setter and getter
方法的实现name
属性的setter
方法是setname
,它操作的是_name
这个变量-
在
@synthesize
中定义与变量名不同的setter
和getter
的命名,以此来保护变量不会被不恰当的访问(setter=<name>
这种不常用,也不推荐使用)//setter=<name>这种不常用,也不推荐使用 @property (nonatomic, setter = mysetter,getter = mygetter ) nsstring *name; @property (nonatomic,getter = ishidden ) bool hidden;
-
@property
有两个对应的词,一个是@synthesize
,一个是@dynamic
。如果@synthesize
和@dynamic
都没写,那么默认的就是@syntheszie var = _var
。如果某属性已经在某处实现了自己的
setter/getter
,可以使用@dynamic
来阻止@synthesize
自动生成新的setter/getter
覆盖。
@dynamic
告诉编译器:属性的setter
与getter
方法由用户自己实现,不自动生成。(当然对于readonly
的属性只需提供getter
即可)。假如一个属性被声明为
@dynamic var
,然后你没有提供@setter
方法和@getter
方法。编译的时候没问题,但是当程序运行到instance.var = somevar
,由于缺setter
方法会导致程序崩溃。或者当运行到somevar = var
时,由于缺getter
方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
// man.h #import <foundation/foundation.h> @interface man : nsobject @property (nonatomic,strong)nsstring *name; @end // man.m #import "man.h" @implementation man @dynamic name; // setter // - (void)setname:(nsstring *)name // { // _name = name; // } // getter - (nsstring *)name { return _name; } @end
调用时会出现崩溃
man *man = [[man alloc] init]; man.name = @"sky";//缺 setter 方法会导致程序崩溃 nsstring *name = man.name;//缺 getter 方法同样会导致崩溃
属性特质
原子性
atomic
(默认):atomic
意为操作是原子的,意味着只有一个线程访问实例变量(生成的setter
和getter
方法是一个原子操作)。atomic
是线程安全的,至少在当前的存取器上是安全的。它是一个默认的特性,但是很少使用,因为比较影响效率。nonatomic
:nonatomic
意为操作是非原子的,可以被多个线程访问。它的效率比atomic
快。但不能保证在多线程环境下的安全性,开发中常用。开发ios程序时应该使用
nonatomic
属性,因为atomic
(同步锁)属性严重影响性能。该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic
可以节省这些虽然很小但是不必要额外开销。
存取器控制
readwrite
(默认):readwrite
是默认值,表示该属性同时拥有setter
和getter
。readonly
:readonly
表示只有getter
没有setter
。有时候为了语意更明确可能需要自定义访问器的名字。
//setter=<name>这种不常用,也不推荐使用 @property (nonatomic, setter = mysetter,getter = mygetter ) nsstring *name; @property (nonatomic,getter = ishidden ) bool hidden;
assign
(默认):assign
用于非指针变量(值)类型,统一由系统栈进行内存管理。一般用于基础类型和c
数据类型,如int
、float
、double
和nsinteger
,cgfloat
等表示单纯的复制。还包括不存在所有权关系的对象,比如常见的delegate
。retain
:在setter
方法中,需要对传入的对象进行引用计数加1
的操作。strong
:strong
是在ios
引入arc
的时候引入的关键字,是retain
的一个可选的替代。对传入的对象的强引用,会增加对象的引用计数。strong
跟retain
的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。weak
:对传入的对象的弱引用,不增加对象的引用计数,也不持有对象,当对象消失后指针自动指向nil
。copy
:与strong
类似,但区别在于copy
是创建一个新对象,strong
是创建一个指针,引用对象计数加1
。
举例说明weak
与strong
与copy
属性特质的差异
- 首先创建两个自定义的
person
类的实例变量,并分别用weak
与strong
修饰。
@property (nonatomic,strong) person *strongperson; @property (nonatomic,weak) person *weakperson;
- 将
strongperson
属性置nil
。
self.strongperson = [[person alloc] init]; self.weakperson = self.strongperson; self.strongperson = nil; nslog(@"strongstr=%@,weakstr=%@",self.strongperson,self.weakperson);
输出结果为:strongstr=(null),weakstr=(null)
。说明weak修饰的属性并不会使引用计数增加。
- 如果使用nsstring类进行上述类似操作,得到的结果是不同的。
@property (nonatomic,strong) nsstring *strongstr; @property (nonatomic,weak) nsstring *weakstr; ··· self.strongstr = @"string"; self.weakstr = self.strongstr; self.strongstr = nil; nslog(@"strongstr=%@,weakstr=%@",self.strongstr,self.weakstr);
输出结果为:strongstr=(null),weakstr=string
。这里主要是因为nsstring类型的赋值默认会加上copy,而copy会创建一个新的对象。这里的赋值语句其实是
self.strongstr = [@"string" copy]; self.weakstr = [self.strongstr copy];
- 将
weakperson
属性置nil
。
self.strongperson = [[person alloc] init]; self.weakperson = self.strongperson; self.weakperson = nil; nslog(@"strongstr=%@,weakstr=%@",self.strongperson,self.weakperson);
输出结果如下:strongstr=<person: 0x600000007d50>,weakstr=(null)
。说明weak修饰的属性只是对对象的弱引用,并不会真正的持有该对象。
- 新建一个
person
类实例变量p
,赋值strongperson
后将p
置nil
。
person *p = [[person alloc] init]; self.strongperson = p; self.weakperson = self.strongperson; p = nil; nslog(@"strongstr=%@,weakstr=%@",self.strongperson,self.weakperson);
输出结果为:strongstr=<person: 0x600000200b50>,weakstr=<person: 0x600000200b50>
。因为strong
属性会强引用该对象并使该对象的引用计数+1
,所以即使把p
设置为nil
,该对象也并没有释放,要想释放该对象,还得把strongstr
设置为nil:self.strongperson = nil;
。这样输出结果才为 strongstr=(null),weakstr=(null)
。
- 在给
person
类加了一个name
属性。并用copy
修饰 :(@property (nonatomic,copy) nsstring *name
)。
nsstring *a = @"xiaoming"; person *p = [[person alloc] init]; p.name = a; nslog(@"before p.name=%@",p.name); a = @"xiaohua"; nslog(@"after p.name=%@",p.name);
输出结果:before p.name=xiaoming
与after p.name=xiaoming
。因为copy
关键字修饰的属性是将对象拷贝一份赋值,所以你改变原对象并不会对拷贝后的对象有任何改变。
注:用@property
声明 nsstring
、nsarray
、nsdictionary
经常使用copy
关键字,是因为他们有对应的可变类型:nsmutablestring
、nsmutablearray
、nsmutabledictionary
,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份.
要点
可以用
@property
语法来定义对象中所封装的数据。通过“修饰词”来指定存储数据所需的正确语义。
在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。
开发ios程序时应该使用
nonatomic
属性,因为atomic
(同步锁)属性严重影响性能。