屬性修飾符簡(jiǎn)述
ios5之前是MRC,內(nèi)存需要程序員進(jìn)行管理,ios5之后是ARC,除非特殊情況,比如C框架或者循環(huán)引用,其他時(shí)候是不需要程序員手動(dòng)管理內(nèi)存的。
? ios中當(dāng)我們定義屬性@property的時(shí)候就需要屬性修飾符,下面我們就看一下不同屬性修飾符的作用。有錯(cuò)誤和不足的地方還請(qǐng)大家諒解并批評(píng)指正。
主要的屬性修飾符有下面幾種:
- copy
- assign
- retain
- strong
- weak
- readwrite/readonly (讀寫策略、訪問(wèn)權(quán)限)
- nonatomic/atomic (安全策略)
如果以MRC和ARC進(jìn)行區(qū)分修飾符使用情況,可以按照如下方式進(jìn)行分組:
1. MRC: assign/ retain/ copy/ readwrite、readonly/ nonatomic、atomic 等。
2. ARC: assign/ strong/ weak/ copy/ readwrite、readonly/ nonatomic、atomic 等。
屬性修飾符對(duì)retainCount計(jì)數(shù)的影響。
- alloc為對(duì)象分配內(nèi)存,retainCount 為1 。
- retain MRC下 retainCount + 1。
- copy 一個(gè)對(duì)象變成新的對(duì)象,retainCount為 1, 原有的對(duì)象計(jì)數(shù)不變。
- release 對(duì)象的引用計(jì)數(shù) -1。
- autorelease 對(duì)象的引用計(jì)數(shù) retainCount - 1,如果為0,等到最近一個(gè)pool結(jié)束時(shí)釋放。
不管MRC還是ARC,其實(shí)都是看reference count是否為0,如果為0那么該對(duì)象就被釋放,不同的地方是MRC需要程序員自己主動(dòng)去添加retain 和 release,而ARC apple已經(jīng)給大家做好,自動(dòng)的在合適的地方插入retain 和 release類似的內(nèi)存管理代碼,具體原理如下,圖片摘自官方文檔。

下面就詳述上所列的幾種屬性修飾符的使用場(chǎng)景,應(yīng)用舉例和注意事項(xiàng)。
屬性修飾符詳述
一、copy
使用場(chǎng)景
- 一般情況下,copy可以用于對(duì)不可變?nèi)菀椎膶傩孕揎椫?,主要是NSArray /NSDictionary/NSString, 也可以用來(lái)修飾block。
- 在MRC和ARC下都可以使用。
- 其setter方法,與retain處理流程一樣,先舊值release,再copy出新的對(duì)象。
應(yīng)用舉例
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) void(^typeBlock)(BOOL selected);
@property (nonatomic, copy) void(^cancelBlock)();
注意事項(xiàng)
- 要注意的就是深淺拷貝,這個(gè)也是使用copy這個(gè)屬性修飾符最重要的地方,這以后會(huì)在另一篇文章里面單獨(dú)講。
- MRC 和 ARC 都可以用copy。
- copy下的setter方法。
-(void)setName: (id)newName {
if (name != newName) {
[name release];
name = [newName copy];
}
}
- 用copy修飾block時(shí)在MRC和ARC下的區(qū)別
MRC環(huán)境下
(1)block訪問(wèn)外部局部變量,block存放在棧里面。
(2)只要block訪問(wèn)整個(gè)app都存在的變量,那么肯定是在全局區(qū)。
(3)不能使用retain引用block,因?yàn)閎lock不在堆區(qū)里面,只有使用copy才會(huì)把block放在堆區(qū)里面。ARC環(huán)境下
(1)只要block訪問(wèn)外部局部變量,block就會(huì)存放在堆區(qū)。
(2)可以使用strong去引用,因?yàn)楸旧砭鸵呀?jīng)存放在堆區(qū)了。
(3)也可以使用copy進(jìn)行修飾,但是strong性能更好。
- 當(dāng)使用block的時(shí)候注意循環(huán)引用,引起內(nèi)存無(wú)法釋放,造成內(nèi)存泄漏。
AddSignHeaderView.h文件中定義block
@property (nonatomic, copy) void (^addMembersBtnOnClick)();
AddSignViewController.m文件中調(diào)用block
// 懶加載控件
- (AddSignHeaderView *)headerView {
if (!_headerView) {
_headerView = [[AddSignHeaderView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, 170)];
}
return _headerView;
}
// 調(diào)用block
-(void) viewDidLoad {
__weak typeof(self) weakSelf = self;
self.headerView.addMembersBtnOnClick = ^() {
AddSignContactsSelectVC *addSign = [[AddSignContactsSelectVC alloc] initWithBlockSelectedUsernames:weakSelf.contactsSource];
addSign.hidesBottomBarWhenPushed = YES;
addSign.title = @"選擇聯(lián)系人";
addSign.delegate = weakSelf;
[weakSelf.navigationController pushViewController:addSign animated:YES];
};
}
下面說(shuō)一下,為什么會(huì)引起循環(huán)引用?
? ?從上面我們可以看到,controller引用了headerView,headerView里面擁有block屬性,在執(zhí)行block時(shí)候,又引用了self,這就造成了循環(huán)引用,相互引用造成循環(huán)引用,內(nèi)存泄漏,如下圖所示。

想解決循環(huán)引用的問(wèn)題,就是打破這個(gè)引用循環(huán)。將self進(jìn)行弱化__weak typeof(self) weakSelf = self,如下圖所示。

二、assign
使用場(chǎng)景
- 在MRC 和 ARC下都可以使用。
- 一般用來(lái)修飾基礎(chǔ)數(shù)據(jù)類型(NSInteger, CGFloat) 和 C數(shù)據(jù)類型(int ,float, double)等。它的setter方法直接賦值,不進(jìn)行任何retain操作。
應(yīng)用舉例
@property (nonatomic, assign) NSInteger studentNum;
@property (nonatomic, assign) CGFloat cellHeight;
注意事項(xiàng)
- MRC 和 ARC 都可以用assign。
- assign下的setter方法:
-(void)setName :(id)str
{
name = str;
}
三、retain
使用場(chǎng)景
- 一般情況下,retain用在MRC情況下,被retain修飾的對(duì)象,引用計(jì)數(shù)retainCount要加1的。
- retain只能修飾oc對(duì)象,不能修飾非oc對(duì)象,比如說(shuō)CoreFoundation對(duì)象就是C語(yǔ)言框架,它沒(méi)有引用計(jì)數(shù),也不能用retain進(jìn)行修飾。
- retain一般用來(lái)修飾非NSString 的NSObject類和其子類。
應(yīng)用舉例
@property (nonatomic, retain) DDDemoObject *modelObject;
注意事項(xiàng)
1. 要注意的就是循環(huán)引用造成的內(nèi)存泄漏,對(duì)于兩個(gè)對(duì)象A和B,如果A對(duì)象中引用B對(duì)象,并且用retain修飾;B對(duì)象中引用A對(duì)象,并且也用retain修飾。這個(gè)時(shí)候就是A和B相互引用,無(wú)法釋放,造成內(nèi)存泄漏。

如上圖所示,A和B相互引用,造成A和B的引用計(jì)數(shù)都不為0,無(wú)法釋放而留在內(nèi)存中,造成內(nèi)存泄漏,當(dāng)這種內(nèi)存泄漏很嚴(yán)重時(shí),會(huì)出現(xiàn)閃退等問(wèn)題。
解決辦法:將A和B其中的一端改成assign進(jìn)行修飾,打斷這個(gè)循環(huán)引用的鏈,就解決了循環(huán)引用的問(wèn)題。

如上圖所示,由于B引用A的時(shí)候用的是assign進(jìn)行修飾,那么A的引用計(jì)數(shù)可以為0,那么自然就解除了A對(duì)B的強(qiáng)引用,B的retainCount也可以為0,就解決了內(nèi)存泄漏的問(wèn)題。
2. 下面說(shuō)一下MRC下assign和retain的區(qū)別:assign只是簡(jiǎn)單的賦值操 作,它引用的對(duì)象被釋放,會(huì)造成野指針,可能出現(xiàn)crash情況;retain會(huì)使對(duì)象的retainCount計(jì)數(shù)加1,獲得對(duì)象的擁有權(quán),只有對(duì)象的引用計(jì)數(shù)為0的時(shí)候才會(huì)被釋放,避免訪問(wèn)一個(gè)被釋放的對(duì)象。
3. retain下的setter方法
-(void) setName: (id) nameStr
{
if (name != nameStr) {
[name release];
name = [nameStr retain];
}
}
四、strong
使用場(chǎng)景
- strong表示對(duì)對(duì)象的強(qiáng)引用。
- ARC下也可以用來(lái)修飾block,strong 和 weak兩個(gè)修飾符默認(rèn)是strong。
- 用于指針變量,setter方法對(duì)參數(shù)進(jìn)行release舊值再retain新值。
應(yīng)用舉例
@property (nonatomic, strong) NSArray *dataArr;
@property (nonatomic, strong) NSMutableArray *btnArray;
@property (nonatomic, strong) UILabel *descLabel;
// 對(duì)于控件也可以用weak,因?yàn)閏ontroller已經(jīng)對(duì)root view有一個(gè)強(qiáng)引用,view addSubview 子控件,所以即使用weak也不會(huì)提前釋放。
@property (nonatomic, strong) CompleteDatePicker *preciseDatePicker;
// CompleteDatePicker在這里是自定義類。
@property (nonatomic ,strong) NSString *signupId;
// 字符串除了用copy,用strong也是可以的。
注意事項(xiàng)
- strong修飾的屬性,對(duì)屬性進(jìn)行的是強(qiáng)引用,對(duì)象的引用計(jì)數(shù)retainCount + 1;
- 注意兩個(gè)對(duì)象之間相互強(qiáng)引用造成循環(huán)引用,內(nèi)存泄漏。
五、weak
使用場(chǎng)景
- weak 表示對(duì)對(duì)象的弱引用,被weak修飾的對(duì)象隨時(shí)可被系統(tǒng)銷毀和回收。
- weak比較常用的地方就是delegate屬性的設(shè)置。
- 用weak修飾弱引用,不會(huì)使傳入對(duì)象的引用計(jì)數(shù)加1。
應(yīng)用舉例
@protocol DDCollegePickerVCDelegate <NSObject>
- (void)didSelectedCollegePicker:(DDCollegePickerVC *)picker
collegeID:(NSString *)collegeID
collegeName:(NSString *)collegeName;
@end
@interface DDCollegePickerVC : UIViewController
@property (nonatomic, weak) id <DDCollegePickerVCDelegate> delegate;
@property (nonatomic, weak) UIView *inputView;
@end
// 上面自定義一個(gè)protocol DDCollegePickerVCDelegate,且在interface中將delegate屬性定義為weak,并且定義了一個(gè)inputView的控件。
注意事項(xiàng)
- 下面說(shuō)一下前面所述的assign和weak的區(qū)別:當(dāng)它們指向的對(duì)象釋放以后,weak會(huì)被自動(dòng)設(shè)置為nil,而assign不會(huì),所以會(huì)導(dǎo)致野指針的出現(xiàn),可能會(huì)導(dǎo)致crash。
- 下面說(shuō)一下strong和weak的區(qū)別:
- strong :表明是一個(gè)強(qiáng)引用,相當(dāng)于MRC下的retain,只要被strong引用的對(duì)象就不會(huì)被銷毀,當(dāng)所有的強(qiáng)引用消除時(shí),對(duì)象的引用計(jì)數(shù)為0時(shí),對(duì)象才會(huì)被銷毀。
- weak : 表明是一個(gè)弱引用,相當(dāng)于MRC下的assign,不會(huì)使對(duì)象的引用計(jì)數(shù)+1。
- 兩個(gè)不同對(duì)象相互strong引用對(duì)象,會(huì)導(dǎo)致循環(huán)引用造成對(duì)象不能釋放,造成內(nèi)存泄漏。
六、readwrite/readonly
使用場(chǎng)景
1. 當(dāng)我們用readwrite修飾的時(shí)候表示該屬性可讀可改,用readonly修飾的時(shí)候表示這個(gè)屬性只可以讀取,不可以修改,一般常用在我們不希望外界改變只希望外界讀取這種情況。
2. readwrite 程序自動(dòng)創(chuàng)建setter/getter方法,readonly 程序創(chuàng)建getter方法。此外還可以自定義setter/getter方法。
3. 系統(tǒng)默認(rèn)的情況就是 readwrite。
應(yīng)用舉例
1. 一般我們封裝屬性只希望外界能看到,自己能夠修改的時(shí)候,在.h文件里用readonly修飾,在.m文件里面用readwrite修飾。


由上兩圖可知,m文件內(nèi)部readwrite修飾屬性cityName,可以修改屬性值,h文件暴露在外面的是onlyread屬性,這樣外面只能讀取不能修改該屬性值。
注意事項(xiàng)
1. 當(dāng)希望外界能讀取我們這個(gè)屬性,但是不希望被外界改變的時(shí)候就用readonly。
七、nonatomic/atomic
使用場(chǎng)景
1. nonatomic 非原子屬性。它的特點(diǎn)是多線程并發(fā)訪問(wèn)性能高,但是訪問(wèn)不安全;與之相對(duì)的就是atomic,特點(diǎn)就是安全但是是以耗費(fèi)系統(tǒng)資源為代價(jià),所以一般在工程開發(fā)中用nonatomic的時(shí)候比較多。
2. 系統(tǒng)默認(rèn)的是atomic,為setter方法加鎖,而nonatomic 不為setter方法加鎖。
3. 如1所述,使用nonatomic要注意多線程間通信的線程安全。
應(yīng)用舉例
// 這個(gè)例子就比較多了,基本上我們項(xiàng)目中都用nonatomic,雖然setter方法不安全但是性能高。
@property (nonatomic, strong) UIImage *imagePicture;
@property (nonatomic, strong) UIImageView *pictureLinkView;
@property (nonatomic, strong) UILabel *labelLocation;
@property (nonatomic, strong) UILabel *labelCreateTime;
@property (nonatomic, strong) UIButton *btnDelete;
@property (nonatomic, strong) UIButton *btnInteract;
@property (nonatomic, strong) DDCommentMenuView *menuView;
@property (nonatomic, strong) DDShareLinkView *shareLinkView;
上面舉了幾個(gè)例子,實(shí)際上很多,具體工程中都用nonatomic,所以大家就不用糾結(jié)這兩個(gè)修飾符到底用哪一個(gè)了。
注意事項(xiàng)
1. 為了提高性能,一般我們就用nonatomic。所以對(duì)這個(gè)屬性修飾符我們可以不必過(guò)于糾結(jié)。
2. 注意atomic設(shè)置成員變量的@property屬性,提供多線程安全。在多線程中,原子操作是必須的。加入atomic屬性修飾符以后,setter函數(shù)會(huì)變成下面這樣:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
之所以這么做,是因?yàn)榉乐乖趯懳赐瓿傻臅r(shí)候被另外一個(gè)線程讀取,造成數(shù)據(jù)錯(cuò)誤。
3. 下面說(shuō)一下為什么nonatomic要比atomic快。原因是:它直接訪問(wèn)內(nèi)存中的地址,不關(guān)心其他線程是否在改變這個(gè)值,并且中間沒(méi)有死鎖保護(hù),它只需直接從內(nèi)存中訪問(wèn)到當(dāng)前內(nèi)存地址中能用到的數(shù)據(jù)即可(可以理解為getter方法一直可以返回?cái)?shù)值,盡管這個(gè)數(shù)值在cpu中可能正在修改中)
4. 不要誤認(rèn)為多線程下加atomic是安全的,這樣理解是不正確的,說(shuō)明理解的不夠深入。atomic的安全只是在getter和setter方法的時(shí)候是原子操作,是安全的。但是其他方面是不在atomic管理范圍之內(nèi)的,例如變量cnt的++運(yùn)算。這個(gè)時(shí)候不能保證安全。
@property int cnt;
@synthesize cnt = _cnt;
self.cnt = 0;
for (i = 0; i < n; i++) {
self.cnt ++;
}
這里線程就不是安全的,想要線程安全就得加鎖,加鎖的技術(shù)以后會(huì)專門寫一篇文章。
致謝
文章雖然很簡(jiǎn)單,但是時(shí)間倉(cāng)促難免有所紕漏,有錯(cuò)誤的地方敬請(qǐng)指正,希望可以相互學(xué)習(xí),共同進(jìn)步,同時(shí)謝謝朋友和同事的關(guān)心。