getter / setter
readwrite / readonly
atomic / nonatomic
assign / strong / weak / retain / copy
其它临时变量
getter、setter
@property (nonatomic, assign, getter=isLoading ,setter=loading:) BOOL loading;
重命名了loading的getter方法。所以可以通过下面方法来得到loading的值。
if (self.isLoading) { } if ([self isLoading]) { } if (self.loading) { }
重命名了loading的setter方法。所以可以通过下面方法来赋值。
- (void)loadingIs:(BOOL)loading{ _loading= loading; }
readwrite 、 readonly
读写性修饰符:
readwrite:表明这个属性是可读可写的,系统为我们生成这个属性的setter和getter方法以及下划线开头的成员变量。
• readonly:表明这个属性只能读不能写,系统只为我们生成一个getter方法下划线开头的成员变量,不会创建setter方法
atomic 、nonatomic
原子性修饰符atomic 、nonatomic。在定义的时候不写atomic
或者nonatomic
,默认是atomic
。
atomic可以保证读/写安全。二者均不能保证线程安全,例如release。
对比
atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。
如果你自己写 getter/setter,那atomic/nonatomic/retain/assign/copy (setter相关修饰符
)这些关键字只起提示作用,写不写都一样。
但是我们不可能每个属性都自己写setter getter或许还是要看看的。
多线程管理(苹果在一定程度上屏蔽了多线程操作)
Nonatomic:高性能,一般使用这个
Atomic:低性能
对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。
保证数据完整性——这个多线程编程的最大挑战之一——往往还需要借助其他手段。
Atomic
1.是默认的
2.原子属性
3.为setter方法加锁 (默认) (类似多线程中的互斥锁)
4. 速度不快,因为要保证操作整体完成
5.线程安全(不是绝对的),但需要消耗大量资源。会保证 CPU 能在别的线程来访问这个属性之前,先执行完当前流程
Non-Atomic
1.不是默认的
2.非原子属性
3.不为setter方法加锁
4.更快
4.非线程安全,适合内存小的移动设备。如有两个线程访问同一个属性,会出现无法预料的结果
例子1
假设有一个 atomic 的属性 “name”,如果线程 A 调[self setName:@"A"]
,线程 B 调[self setName:@"B"]
,线程 C 调[self name]
,那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 name 是读/写安全的。
但是,如果有另一个线程 D 同时在调[name release]
,那可能就会crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
如果 name 属性是 nonatomic 的,那么上面例子里的所有线程 A、B、C、D 都可以同时执行,可能导致无法预料的结果。如果是 atomic 的,那么 A、B、C 会串行,而 D 还是并行的。
例子2
nonatomice代码:
//@property(nonatomic, retain) UITextField *userName; //系统生成的代码如下: - (UITextField *) userName { return userName; } - (void) setUserName:(UITextField *)userName_ { [userName_ retain]; [userName release]; userName = userName_; }
atomic代码:
//@property(retain) UITextField *userName; //系统生成的代码如下: - (UITextField *) userName { UITextField *retval = nil; @synchronized(self) { retval = [[userName retain] autorelease]; } return retval; } - (void) setUserName:(UITextField *)userName_ { @synchronized(self) { [userName release]; userName = [userName_ retain]; } }
简单来说,就是 atomic 会加一个锁来保障线程安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调 setter,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。
要注意那个锁并不能『保证线程安全』。
assign / strong / weak / retain / copy
assign
用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型。 eg:int、bool
strong
OC对象类型(NSArray、NSDate、NSNumber、模型类)
一个对象只要有强指针引用着,就不会被销毁 。
weak
弱引用
在弱引用的时候使用weak。例如代理、block(防止循环引用)。
block也会经常导致循环引用,所以通常的做法就是,在外部创建一个weakSelf(用__weak修饰的self),来防止循环引用。这里最好在block内部再声明一个strongSelf(用__strong来修饰weakSelf).这是因为保证代码在执行block期间,self不会被释放,当block执行完后,会自动释放该strongSelf;
UI控件
在创建UI空间的时候,准确的来说是使用weak修饰的。但是如果我们的操作稍有不慎,控件容易释放。这也是为什么大部分人都使用strong进行修饰的原因。
但是使用strong就会使空间的引用计数多+1,导致释放不及时,但是在父试图消失的时候,空间还是会释放的。所以对内存要求不大,没有强迫症的人可以使用strong,简单粗暴。
reatin
retain:针对对象类型进行内存管理(非ARC)。
当给对象类型使用此修饰符时,setter方法会先将旧的对象属性release掉,再对新的对象进行一次赋值并进行一次retain操作
retain是指针的复制。
copy
copy是内容的复制。
copy语法的作用:产生副本。 且copy返回的是不可变的副本,mutableCopy返回的是可变的副本。
修改了副本并不会影响源对象,修改了源对象,并不会影响副本。
1. 一般用在NSString*类型、block类型上。
1.1 NSString
注意:并不是所有情况下我们的string都必须使用copy,因为如果我们的需求是希望string是随着我的改变而改变的,那么这个时候应该使用strong。 在setter方法中会判断 传入的字符串 是否是可变的。 如果是可变的就分配新的内存再赋值 如果是不可变的就直接赋值地址 而实际上开发中其实大量使用的是不可变的字符串 所以最好使用 strong 修饰字符串,可以提升性能(减少一次判断)。
对源头是NSMutableString的字符串,retain仅仅是指针引用,增加了引用计数器,这样源头改变的时候,用这种retain方式声明的变量(无论被赋值的变量是可变的还是不可变的),它也会跟着改变;
而copy声明的变量,它不会跟着源头改变,它实际上是深拷贝。
对源头是NSString的字符串,无论是retain声明的变量还是copy声明的变量,当第二次源头的字符串重新指向其它的地方的时候,它还是指向原来的最初的那个位置,也就是说其实二者都是指针引用,也就是浅拷贝。
另外说明一下,这两者对内存计数的影响都是一样的,都会增加内存引用计数,都需要在最后的时候做处理。
其实说白了,对字符串为啥要用这两种方式?我觉得还是一个安全问题,比如声明的一个NSString *str变量,然后把一个NSMutableString *mStr变量的赋值给它了,如果要求str跟着mStr变化,那么就用retain;如果str不能跟着mStr一起变化,那就用copy。而对于要把NSString类型的字符串赋值给str,那两都没啥区别。不会影响安全性,内存管理也一样。
1.2 block
内存分为五个区:栈区、堆区、常量区、代码区、静态区(全局区)。
Block用copy修饰可以拷贝到堆区,以便我们程序员管理。
它本身默认是在栈区,由系统管理,什么时候释放不确定,为了避免访问野指针,所以应该把它放在堆区。
用copy修饰Block时—->首先要知道Block在非ARC和ARC下的区别。
非ARC环境下:
block访问外部局部变量,block存放栈里面
只要block访问变量,而且是整个app都存在的变量,那么肯定在全局区
在非ARC中.不能使用retain引用block,因为不会放在堆里面,在非ARC中只能使用copy,才会把block放在堆里面。
ARC环境下:
只要block访问了外部局部变量,block就会存放到堆里面 可以使用strong去引用 因为本身就已经是存放在堆区了 也可以用copy,但是用stong性能更好
注意:在ARC下,声明出来的block属性,不管用strong还是copy,编译器都会对这个block进行copy操作。即便如此,还是推荐使用copy来修饰,这样也能提醒你,编译器会对block进行copy操作。
2. 类的copy:
如果我们想实现类的copy,必须实现一个方法:-(id)copyWithZome:(NSZone*)zone;
这是为什么呢?
我们去 NSString 中去寻找答案,那么我们会发现其实 NSString 已经遵守了 NSCopying 与 NSMutableCopying 的协议,我们主要看 NSCopying ,进入这个协议之后你会发现 -(id)copyWithZome:(NSZone*)zone
这个方法,也就是说NSString 已经遵守了协议的这个方法,所以才能直接实现 copy 的方法。所以如果想实现自定义类的 copy 方法,我们是需要先遵守 NSCopying 协议,然后实现-(id)copyWithZome:(NSZone*)zone
的方法:
-(id)copyWithZone:(NSZone *)zone{ Mitchell*copyMit = [[Mitchell allocWithZone:zone] init]; copyMit.name = self.name; return copyMit; }
zone:系统返回给我们 copy 对象的内存空间
注意:必须在初始化方法中给属性赋值,才能让 copy 出的对象和原来的对象有相同的属性。
3. 再说一下 copy 类中的 set 方法
如果属性是 copy 的,那么系统默认只会在 set 方法中调用 copy 的方法:
-(void)setName:(Mitchell*)name{ _name = [name copy]; }
其它临时变量
strong,weak, unsafe_unretained往往都是用来声明属性的,
如果想声明临时变量就得用__strong, __weak, __unsafe_unretained, __autoreleasing。