文章目录
  1. 1. @property的前世今生
  2. 2. @property的本质
  3. 3. @property在@protocol和category中如何使用
  4. 4. @property的修饰符
    1. 4.1. strong、weak和retain、assign的理解,copy的理解(引用关系)
      1. 4.1.1. strong和retain
      2. 4.1.2. weak和assign的理解
      3. 4.1.3. copy的理解
    2. 4.2. nonatomic的理解(原子操作)
    3. 4.3. readonly、readWrite(可读性)
    4. 4.4. 重写存取方法(setter、getter)
    5. 4.5. 常见属性的修饰符
      1. 4.5.1. block
      2. 4.5.2. NSString
      3. 4.5.3. 代理属性
      4. 4.5.4. 集合类及深浅拷贝的讨论(代表有NSArray和NSMutableArray)
  5. 5. 参考内容
    1. 5.1. 参考博文
    2. 5.2. 开发文档

@property(属性)这个iOS开发中最常用的东西肯定都不陌生~那大家聊一下属性及其修饰符,肯定也是说起来一套一套的,然而是不是都真的理解了属性,还有属性的修饰符的意义呢?可以用以下几个问题来考察一下自己:

  • @property到底什么东西?它后面可以有哪些修饰符?
  • 什么情况使用weak关键字,相比assign有什么不同?
  • 怎么用copy关键字?哪些时候可以用,哪些时候必须用,哪些时候用了会出问题?
  • 常见的一些属性,例如代理、Block、字典数组集合、字符串应该用什么修饰符?又是为什么呢?

是不是感觉有点虚了,那好接下来就一起来聊一聊属性和属性修饰符的相关问题~

@property的前世今生

说到属性,就不得不讲成员变量,何为成员变量?因为Objective-C是面向对象开发,当你需要构建类的时候呢,就肯定需要空间来储存类的相关信息的,并且还可能修改它读取它,那通过什么来操作这些空间呢?那就是成员变量(指针).这个深究起来可能就远了,因为OC语言是从C扩展来的,所以继承了C语言的很多特色,尤其是C的主要特性–指针.而Objective-C的类创建并调用实例的时候是指针,那类的成员变量呢,其实也是指针,说白了就是指针指向了一堆空间(类实例),然后该空间里面包含了很多小空间,并且有指针指着(成员变量指针).大概就是这么个意思吧~

接下来就得从Xcode和Objective-C还小的时候说起了,开始的时候它们呢并不像现在这样用起来这么方便,当时Xcode并不能自动合成操作方法(就是所谓的设置方法setter和读取方法getter,就是现在通过self点来点去的时候调用的方法self.view ...),最开始的时候是以下风格的,需要工程师自己来写相应存取方法,这也是为什么老一辈的iOS工程师都是这么个写法的原因,这里还涉及到了一些工程优化的问题,暂不赘述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface ViewController (){
UIView * _myCeshiView;
}
@end

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_myCeshiView = [[UIView alloc] init];
}
- (void)setmyCeshiView:(UIView *)view{
}
- (UIView *)myCeshiView{
}
@end

后来呢,Xcode和它的编译器小伙伴就厉害了一些,可以通过一个叫做@synthesize的关键字,来自动帮你生成对应成员变量的存取方法了,如下所示:(这也是现在遇到把系统关键字作为属性的情况的常用处理手段)(同时也要提一下@dynamic关键字,他的作用和@synthesize正好相反,就是告诉编译器存取方法运行时会有的,由工程师自己来生成):

1
2
3
@implementation ViewController
@synthesize mynumber;
@end

当然了,Xcode和它的编译器小伙伴儿也在努力完善自己,现在的编译器已经可以帮我们自动做好以下工作:生成方法代码getter、setter,自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字.并且工程师也有了一个更简单的写法如下:

1
2
3
@interface ViewController : UIViewController
@property (assign, nonatomic) NSInteger mynumber;
@end

@property的本质

那么,到底什么是@property呢?通过以上的内容,其实不难总结出来:@property = ivar + setter + getter.其实属性归根结底还是要操作成员变量,只是强大的编译器帮我们做了一些可以自动化合成的操作.

@property在@protocol和category中如何使用

首先在协议和分类中我们是可以添加属性的(并非实例变量).因为在协议和分类中支持添加方法,所以在协议和分类中使用@property其实就是添加了对应的存取方法.
如果我们想让遵守我们协议的对象,实现我们协议中的属性(生成实例变量),那可以使用刚才提到过的关键字来处理:@synthesize myCeshiView = _myCeshiView;
而分类里面不能添加实例变量,所以需要用到运行时方法来处理,其实是把对象看成是存着数据的字典,通过KVC的思想来处理的:

1
2
3
4
5
6
7
- (NSString *)tag {
return objc_getAssociatedObject(self, tagKey);
}

- (void)setTag:(NSString *)tag {
objc_setAssociatedObject(self, tagKey, tag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@property的修饰符

上面已经差不多解释了@property到底是什么的问题,接下来就开始处理@property的修饰符的问题.因为@property的对应存取方法和实例变量是自动生成的,所以可配置的修饰符在这里就起到了尤为重要的作用.先来看看修饰符有哪些,一般有以下几组:

  • 引用性修饰符: copy/retain/weak/strong/assign
  • 读写性修饰符: readonly/readwrite
  • 原子性修饰符: nonatomic/atomic
  • 自定义getter | setter方法名修饰符: getter=method和setter=method

接下来就一组一组的深入了解一下它们的含义和运作机制!

strong、weak和retain、assign的理解,copy的理解(引用关系)

首先就是他们这几个修饰词的关系是互斥的,鱼与熊掌不可兼有.一般情况下都会按照标题所示的两个两个相对的关系来理解.

strong和retain

retain和assign是之前MRC的时候遗留下来的内存管理修饰符.其实retain和现在的strong是一样的用途,都是为了保持强引用,关于内存管理的原理,回头专门开一篇深入研究一下,这里就不多做解释了.两者平常使用过程中并没有太大区别,只不过一个是MRC下使用,另一个是ARC下使用.如果硬要说有一点区别,就是strong中可能加了类似@synchronized的锁,然后如果感兴趣做一下实验的话,ARC下将Block属性配置成retain会造成assign效果,而strong会造成copy效果.(注意:block属性需要配置为copy)

weak和assign的理解

这个就有点老生常谈了.weak是ARC新引入的安全型弱引用,用在需要弱引用的对象属性上,类似xib开发中的@IBOutlet属性就是弱引用,相比较strong的强引用,弱引用并不持有对象,也就是说一旦对象身上的强引用都消失了,那这个对象就会被回收,并且这个对象指针会被置为nil,是防止野指针的安全的弱引用.相比来说,assign在ARC下适用于常量属性,类似int、BOOL、NSInteger等.当实例变量回收以后,也不会像weak一样将被回收对象的指针置为nil,所以相对来说不太安全.
所以weak会给回收的对象指针置空,不会让悬垂指针指来指去变成没人管的野指针,而assign在ARC时代,就用来修饰常量属性就好了.

copy的理解

这个修饰符就比较有意思了~每次说到copy必提到深拷贝和浅拷贝,这两个拷贝的定义一直都是混淆不清的,大家各说纷纭,今天来好好研究研究这个copy,然后再在最后分析深浅拷贝的含义~先从copy修饰的属性的setter方法开始看起,如下:

1
2
3
4
5
6
7
8
9
-  (void)setName:(NSString *)name {
if(_name == name){
return;
}
else{
[_name release];
_name = [name copy];
}
}

其实和retain、strong的内部实现很像是不是.其实就是判断传入的对象和既存对象是否一样,如果一样不操作,如果不一样就release旧对象,然后copy新对象给对应属性指针.而retain是持有,指针复制,并且对象retainconter+1,而copy其实是复制内容.这个操作其实是一种限制,因为Objective-C语言的多态特性,所以父类指针指向子类对象很普遍,比防说NSString对象的一个属性,外部可能会传入一个可变的NSMutablestring子对象,为了避免外部修改影响内部属性,就使用copy,这样内部对象是内容复制,其实copy完后内部成员变量其实是不可变的,就不受外界改变的影响了.
关于深浅拷贝在下边一同解释.

nonatomic的理解(原子操作)

包含两个参数,但是只有nonatomic是可以设置的,atomic不用显式设置,属于默认配置,但是显式设置编译器也不会报错.
atomic,为setter方法加锁(默认配置)(类似多线程中的互斥锁)实现一定程度的线程安全,但需要消耗大量资源,一般会在MacApp开发的时候用到.
nonatomic,非原子属性,不为setter方法加锁,非线程安全,适合内存小的移动设备,一般情况下iOS开发都是默认使用nonatomic.
这里呢再深入地讲一点,就是这个atomic是原子性的,但并不是一定”线程安全”的,要实现线程安全,还需要更加深入的线程锁机制来实现.比方说一个atomic属性property,在一个线程A操作下进行了写操作,这时候来了一个线程B也要写操作该属性,确实B线程会等待A线程写完,但是如果A写完了,那么B就进场进行了写操作,当A需要读它写完的property的时候,发现数据已经被B线程篡改了,所以atomic是保证原子操作,并不是保证严格的线程安全.
再深入一点,如果有兴趣可以进一步了解atomic原子操作实现机制,其实就是在存取方法里面加了类似这样的@synchronized(self) {}限制域.至于这个@synchronized(self) {}在单例设计模式里面常用的,这里就不过多解释了.

readonly、readWrite(可读性)

这个就没什么特别大的歧义了,各种资料对于文档的解读和试验已经非常详细了.就是对于属性的可读性的限制,如果是readOnly修饰符修饰的属性,那么编译器将不会生成对应的setter方法,对外只可以读对应属性,并不可写.默认是读写可用的.
还有一点就是,如果使用了对应的修饰符修饰属性,那么如果我们重新定义了setter或者getter方法(比方说懒加载),默认是不会生成对应的以”_”开头的成员变量的.

重写存取方法(setter、getter)

这个呢就是一个固定的用法.setter=xxx;getter=xxxx,就是不用系统自动合成的存取方法,给存取方法取了个别名而已,不做过多的解释了.一般就是为了方便使用,比方要设置BOOL属性的存取方法的时候为了易读就可能会改写一个可读的读写方法名.

常见属性的修饰符

block

使用copy修饰符.说到block可能就得考虑到储存空间的”堆”、”栈”了,老是说什么堆啊栈的,到底神马意思?!其实很简单就是程序运行的时候,数据储存的不同的区域的一种叫法.,是系统维护的,像是函数就有自己的函数栈,Objective-C的类也有自己的类的栈,当函数执行完了,或是类回收了,那么栈内的数据就会释放掉,相当于系统回收了类这种概念;,是程序员自己管理的空间,除非你让他释放掉,他才释放掉,否则就会一直为你所用.而block大多数时候都是需要在声明的类域外使用的,所以怎么办呢,当然是把它放到我们自己管理的堆里面来啊,所以block的属性使用copy修饰符,然后将声明在栈内的block复制到堆,这样我们就可以在声明block的类域外使用它了~回传数值,调用方法~其乐融融~
你说的我都听过,不想看啊!那接下来说点平常不咋看到的内容,就是使用weak修饰block~我嘞个xxxx,怎么还能用weak修饰block,你确定你没有误人子弟?!@~@很明确地告诉你没有.先说用法,block通常用的时候有两个常用用途,回调信息和执行方法.当block里面会有声明类以外的另一个类的相关的参数要回调回去的时候,block属性用copy修饰,意为将其拷贝到堆里面,这样即便栈释放掉了,另一个类的指针也在堆中存在,能够成功的回调回去.如果语法块仅仅是执行而不再回调回去了,比如操作数据库,修改某个系统单例管理类,发送一个通知之类的,则可以用weak来修饰.那为什么不用copy呢,可以用但是不用,原因就是优化内存.你想啊如果这个类要传入1000个block来执行,而这个类又不会马上释放掉的话,用copy是不是就拷贝了1000个block在堆里面?这样就会占用很大一部分内存,如果使用了weak将不必要的block执行后就马上释放掉,相对来说是不是就节约了很多内存呢~涨姿势不~2333333~

NSString

使用copy修饰符.以上copy那里解释的很清楚了,这里就不过多解释了.

代理属性

使用weak修饰符.总的来说就是防止“循环引用”.举个例子,如果代理属性使用strong修饰,比方说有一个A类,持有一个B类,如果当前生成A类实例并持有(A retainconter=1),然后该A类实例生成并持有一个B类实例(B retainconter=1),然后该B类实例的代理是该A类实例(A retainconter=2),那么当释放A实例的时候,(retainconter–,但是retainconter=1),这个时候A实例还持有B实例(B retainconter=1),都释放不掉了,就造成了内存泄露.而使用weak的代理的时候,当A实例的持有者不再持有A实例的时候,B实例对A实例的代理引用是weak弱引用,这时A实例(retainconter=0),就释放掉了,然后B实例不再有持有者,B实例也释放掉了,就不会有内存泄漏啦.

集合类及深浅拷贝的讨论(代表有NSArray和NSMutableArray)

这里只讨论一下深拷贝浅拷贝的问题,关于深拷贝和浅拷贝,每个人理解不同,我比较倾向于这种三个深度的理解:浅拷贝就是指针拷贝,深拷贝就是完全拷贝,个人认为还应该区分出一种中间程度的拷贝叫内容拷贝.这里需要澄清一点就是:copy操作和mutableCopy并非是深拷贝和浅拷贝!!!通过一些对象的拷贝来对比一下就比较容易理解了:

非集合对象:
典型的以NSString为代表.
对mutable非集合对象(e.g.NSMutablestring)的copy和mutableCopy都是深拷贝,就是另外开辟一个空间,并生成一个指向该地址的指针,所以就算是修改原对象,新得到的对象也不受影响,形象化来讲就是有两座房子,每个人一座,自己有自己的钥匙;
而对immutable非集合对象(e.g.NSString)的copy,是浅拷贝,就是生成一个指向同样一块内存地址(也就是对象)的指针,哪个操作改变都会改变原对象,形象化来讲就是两个人合租,两个人每人一把钥匙,都能开这房子的门;而mutableCopy就是深拷贝,同上道理,不赘言了.总结如下:

1
2
3
4
5
[immutableObject copy]        // 浅拷贝
[immutableObject mutableCopy] // 深拷贝

[mutableObject copy] // 深拷贝
[mutableObject mutableCopy] // 深拷贝


集合对象:
集合类对象以数组、字典、集合为代表.
在集合类对象中,对immutable对象进行copy,是指针拷贝,mutableCopy是内容拷贝;对mutable对象进行copy和mutableCopy都是内容拷贝.这里所说的内容拷贝,个人认为内容拷贝的概念独立于深拷贝,是因为内容拷贝并非是严格意义上的深拷贝,其实是指对集合对象的拷贝仅限于对象本身,对象内元素仍然是指针拷贝的.也就是说,我要拷贝数组,把数组重新复制了一份,但是数组内的对象指针指向的还是原数组内对象指针所指向的空间.

1
2
3
4
5
[immutableObject copy]        // 浅拷贝
[immutableObject mutableCopy] // 单层深拷贝 - 内容拷贝

[mutableObject copy] // 单层深拷贝 - 内容拷贝
[mutableObject mutableCopy] // 单层深拷贝 - 内容拷贝

参考内容

参考博文

1.block为什么可以用weak修饰

2.关于 @synchronized,这儿比你想知道的还要多

3.形象解释属性修饰符的内存操作原理

4.最强weak内涵解释!!

开发文档

Transitioning to ARC Release Notes

Objective-C Automatic Reference Counting (ARC)










文章目录
  1. 1. @property的前世今生
  2. 2. @property的本质
  3. 3. @property在@protocol和category中如何使用
  4. 4. @property的修饰符
    1. 4.1. strong、weak和retain、assign的理解,copy的理解(引用关系)
      1. 4.1.1. strong和retain
      2. 4.1.2. weak和assign的理解
      3. 4.1.3. copy的理解
    2. 4.2. nonatomic的理解(原子操作)
    3. 4.3. readonly、readWrite(可读性)
    4. 4.4. 重写存取方法(setter、getter)
    5. 4.5. 常见属性的修饰符
      1. 4.5.1. block
      2. 4.5.2. NSString
      3. 4.5.3. 代理属性
      4. 4.5.4. 集合类及深浅拷贝的讨论(代表有NSArray和NSMutableArray)
  5. 5. 参考内容
    1. 5.1. 参考博文
    2. 5.2. 开发文档