您的位置:首页 > 移动开发 > IOS开发

ios-基础之【13】-iOS的@property的详细解读

2016-12-06 11:15 274 查看
原文:http://blog.talisk.cn/blog/2016/03/05/iOS-@property/

Objective-C的@property的详细解读

文章目录
@property的前世今生
@property与@synthesize
@property与取值、设值方法
@property的特质
原子性
可读、可写权限
内存管理方式
setter和getter方法名
特别注意

@property与KVO

@property在Objective-C中,我们称为属性。它与实例变量有密不可分的关系,从而会涉及到内存管理、KVO等诸多细节问题。

看了很多代码,不少人对使用
@property
的使用习惯和认识也有一些不合理的地方,所以写这篇文章来谈谈
@property
的正确使用姿势。

鄙人水平有限,若文章内容存在错误,欢迎您在评论中指正,我会感激不尽。

@property的前世今生

在Java、C++中我们一般都会为实例变量添加访问权限声明
@public
@private
@protect
,但在Objective-C中,我们通常不会为实例变量添加权限声明的。由于Objective-C是一门动态的语言,允许开发者随时(甚至在运行时)向应用添加实例变量,所以在Objective-C中,没有采用这种方法。

面向对象思想有一个非常重要的性质,封装性。上述的权限声明正是为了保障封装性而产生的。即对外只公开需要公开的属性和方法,内部隐藏起实现细节和并没有必要对外公开的属性和方法。而为了这个封装性,通常情况下我们还会定义为此定义
setter
getter
方法。

@property是一条编译器指令,用于编译器为实例变量自动生成
setter
getter
方法。通常在Objective-C中,我们会在头文件中将需要暴露出来的实例变量的属性
@property
给出,不希望暴露的,可将该实例变量的属性
@property
写在类的实现文件的扩展(extension)中。

举个栗子:

这是.h头文件的内容,希望暴露出的属性写在这里,其他类可以通过.语法来访问
name
birthday
两个属性。

1
2
3
4
5

@interface Student : NSObject

@property (nonatomic, copy) NSString *name;

@end

对于不希望暴露给外界的属性,写在实现文件中,以extension的形式出现。.m实现文件内容如下:

1
2
3
4
56
7
8
9

@interface Student ()

@property (nonatomic, copy) NSString *girlFriendName;

@end

@implementation Student

@end

这样,我们可以在所有import了.h文件的地方使用
name
这个字符串,权限类似于C++中的Public;
girlFriendName
只能在
Student
类的内部使用,即使是
Student
的子类也不能使用,权限类似于C++中的Private。

@property与@synthesize

在有些教程中,我们会发现有提到
@property
@synthesize
要配对使用。然后我们尝试不写
@synthesize
,于是发现仍能正常执行,甚至正常通过“.”点语法(语法糖)和取值、设值方法来访问实例变量。那么问题来了,
@synthesize
的意义到底是什么呢?

查了一下资料,在Xcode 4.0版本之前,确实是不写
@synthesize
不行,后来版本中的LLVM优化了这个语法,只写
@property
不写
@synthesize
也可以自动合成
setter
getter
方法。自动合成的属性所对应的实例变量名就是属性名在前面加“_”,如果我们不想使用这个实例变量名,就可以使用
@synthesize
来指定实例变量名字。如
@synthesize
content = textContent;
。但实例变量中使用下划线作为开头,已经成了广大开发者约定俗成的习惯,所以如果没有特殊需求,建议还是遵循规范,不需要写
@synthesize
,直接声明属性
@property


@property与取值、设值方法

这一块直接举个例子,一目了然地说明:

1
2
3
4
56

@interface Student : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;

@end

4000
这样一段属性声明,等同于自动合成的结果:

1
2
3
4
56
7
8

@interface Student : NSObject

- (NSString *)name;
- (NSDate *)birthday;
- (void)setName:(NSString *)name;
- (void)setBirthday:(NSDate *)birthday;

@end

关于点语法,其实只是Objective-C提供的语法糖。我们在使用点语法访问属性的时候,其实是调用了取值方法“getter”。

@property的特质

我们在前面的例子里,
@property
后跟了一个括号,里面写了几个关键字,这几个关键字正是
@property
的特质。特质规定了自动合成的“setter”与“getter”的具体细节。特质具有四类:原子性、可读可写、内存管理、取设值方法名。

原子性

在操作系统的学习中我们了解到,一个操作具有原子性表明这个操作不可分割,即最基本的操作。这关乎到线程是否安全的问题:多个线程可能同时访问一个属性。如果一个属性不具备原子性,就不会使用同步锁。在iOS开发时,我们极大多数情况都用
nonatomic
非原子性,而在OS X开发时,会使用到
atomic
原子性。这样做的原因是在iOS中使用同步锁,性能开销较大,在极少数需要加锁保护线程安全的时候,我们还需要更底层的锁机制。

可读、可写权限

这个特质有两个选项,
readwrite
readonly
,分别对应读写权限和只读权限。只读权限编译器并不会合成“setter”。默认情况,该特质为
readwrite


内存管理方式

setter方法中赋值的方式,决定了内存管理中引用计数的变化。

这个特质有非常多的选项,我们依次说明。

assign
:只对简单数据类型进行赋值操作。简单数据类型包括
NSInteger
CGFloat
等。
weak
:不对该属性具备拥有关系。此时的setter方法并不会改变对象的引用计数,如果该对象的拥有者全都release了,那么该对象会彻底释放,该weak指针会被置为空
nil

strong
:对该属性具备拥有关系。此时的setter方法会retain新的对象,release旧的对象。如果旧的对象之前引用计数为1,那么在这次重新赋值时就会被彻底释放。
copy
:在setter中,会把该对象复制一份并赋给该属性。我们说复制有两种,『浅拷贝』和『深拷贝』,『浅拷贝』是指对指针的复制,对象本身的内存区域没有变化,新指针指向的还是原有内存区域;『深拷贝』是指对内存区域存储的数据进行的复制,指针会指向新的内存区域。特别注意的是,如果是对可变对象的使用
copy
特质不会改变引用计数,为深拷贝;如果是不可变对象使用
copy
特质,就是浅拷贝,引用计数++,此时和
strong
完全相同。通常情况下,我们对可变对象使用
copy
特质。当类型为
NSString
时,我们也应该对其加以
copy
特质,因为setter中的新值可能是一个
NSMutableString

unsafe_unretained
:不对该属性具备拥有关系,但和
weak
的区别在于指针所指向的对象被彻底释放时,该属性的指针不会被置为空
nil

retain
:这是以前在MRC内存管理方式下的属性,在setter方法中对新值retain。ARC中已经弃用!

关于可变对象要使用
copy
特质:为了保证封装性,setter方法是属性的设值方法,但可变对象可能会在不经过setter方法的情况下,改变自身的值,这样破坏了原有的封装性。所以要对可变对象做copy操作,深拷贝出一份不可变的对象。

setter和getter方法名

可以通过这个特质来指定取值、设值方法的方法名。

用法直接举例:

1

@property (nonatomic, assign, getter = isOpen) BOOL open;

setter设值方法用法和上面例子用法相同,只是不太常用。

特别注意

这些特质都指明了自动合成取设值方法的诸多细节,如果我们开发者想自己实现取设值方法,必须要遵守
@property
中声明的特质,否则会造成误会,甚至奇怪的bug。

@property与KVO

KVO全称是Key-Value Observing,是Objective-C语言中的一种核心机制,我们一般翻译为键值观察。至于KVO的实现方式,相信很多人也知道,就是继承该类并重写setter方法,当调用setter方法的时候,通知监听器回调。前文说到了,
@property
可以自动合成setter,所以这里正是它和KVO之间千丝万缕的关系。

我们访问变量有几种方式,遵循Objective-C调用方法的方式访问
[self variableName]
是一种,
self.variableName
语法糖是一种,直接访问实例变量
_variableName
也是一种,这三种方式有什么区别呢?

方法调用
[self variableName]


如前文所讲,这种方式正是使用了getter方法,通过getter返回值的来访问到实例变量的值,遵循良好的封装性。

点语法糖
self.variableName


正如它的名字一样,这种方式其实只是一种语法糖,其背后根本上还是调用getter方法。

直接访问实例变量
_variableName
不经过取设方法,直接访问实例变量所在内存位置。优点在于不经过方法调用,速度更快。

同样看一下设值的方式呢,也有如下三种:

方法调用
[self setVariableName]


使用setter来设值。

点语法糖
self.variableName = @"talisk"


语法糖,背后同样是setter方法。

直接访问实例变量
_variableName = @"talisk"


不经过设值方法,将新值赋给实例变量所在内存区域。优点还是不经过方法调用,速度更快。然而问题也很明显,用这种方式改变值KVO失效,跳过了setter中内存管理的特质。比如你给某个字符串赋了mutable可变字符串,若该属性内存管理特质为
copy
,此时通过setter设值会深拷贝一份不可变字符串,但直接访问该实例变量不通过setter会导致没有拷贝出一份不可变字符串。

对于这样的问题,我们大概可以总结出最佳使用姿势:

在需要设值的地方,我们通过setter设值方法设值;当需要取值的时候,我们采用直接访问实例变量的方式。

另外要注意,初始化方法中,没有特殊需要,应该尽量直接访问实例变量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: