屬性修飾符簡述
ios5之前是MRC,內(nèi)存需要程序員進行管理重罪,ios5之后是ARC哀九,除非特殊情況勾栗,比如C框架或者循環(huán)引用围俘,其他時候是不需要程序員手動管理內(nèi)存的琢融。
? ios中當我們定義屬性@property的時候就需要屬性修飾符漾抬,下面我們就看一下不同屬性修飾符的作用纳令。有錯誤和不足的地方還請大家諒解并批評指正。
主要的屬性修飾符有下面幾種:
- copy
- assign
- retain
- strong
- weak
- readwrite/readonly (讀寫策略圈匆、訪問權限)
- nonatomic/atomic (安全策略)
如果以MRC和ARC進行區(qū)分修飾符使用情況跃赚,可以按照如下方式進行分組:
1. MRC: assign/ retain/ copy/ readwrite纬傲、readonly/ nonatomic肤频、atomic 等。
2. ARC: assign/ strong/ weak/ copy/ readwrite汁雷、readonly/ nonatomic摔竿、atomic 等继低。
屬性修飾符對retainCount計數(shù)的影響稍走。
- alloc為對象分配內(nèi)存,retainCount 為1 粱胜。
- retain MRC下 retainCount + 1焙压。
- copy 一個對象變成新的對象,retainCount為 1野哭, 原有的對象計數(shù)不變拨黔。
- release 對象的引用計數(shù) -1绰沥。
- autorelease 對象的引用計數(shù) retainCount - 1,如果為0零截,等到最近一個pool結束時釋放疟位。
不管MRC還是ARC甜刻,其實都是看reference count是否為0得院,如果為0那么該對象就被釋放章贞,不同的地方是MRC需要程序員自己主動去添加retain 和 release鸭限,而ARC apple已經(jīng)給大家做好,自動的在合適的地方插入retain 和 release類似的內(nèi)存管理代碼兜喻,具體原理如下朴皆,圖片摘自官方文檔。
下面就詳述上所列的幾種屬性修飾符的使用場景肮疗,應用舉例和注意事項伪货。
屬性修飾符詳述
一超歌、copy
使用場景
- 一般情況下巍举,copy可以用于對不可變?nèi)菀椎膶傩孕揎椫邪妹酰饕荖SArray /NSDictionary/NSString梦皮, 也可以用來修飾block剑肯。
- 在MRC和ARC下都可以使用。
- 其setter方法呀忧,與retain處理流程一樣而账,先舊值release因篇,再copy出新的對象竞滓。
應用舉例
@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) void(^typeBlock)(BOOL selected);
@property (nonatomic, copy) void(^cancelBlock)();
注意事項
- 要注意的就是深淺拷貝商佑,這個也是使用copy這個屬性修飾符最重要的地方莉御,這以后會在另一篇文章里面單獨講。
- MRC 和 ARC 都可以用copy牍颈。
- copy下的setter方法煮岁。
-(void)setName: (id)newName {
if (name != newName) {
[name release];
name = [newName copy];
}
}
- 用copy修飾block時在MRC和ARC下的區(qū)別
MRC環(huán)境下
(1)block訪問外部局部變量画机,block存放在棧里面步氏。
(2)只要block訪問整個app都存在的變量,那么肯定是在全局區(qū)芋类。
(3)不能使用retain引用block侯繁,因為block不在堆區(qū)里面贮竟,只有使用copy才會把block放在堆區(qū)里面咕别。ARC環(huán)境下
(1)只要block訪問外部局部變量重付,block就會存放在堆區(qū)顷级。
(2)可以使用strong去引用凫乖,因為本身就已經(jīng)存放在堆區(qū)了确垫。
(3)也可以使用copy進行修飾,但是strong性能更好帽芽。
- 當使用block的時候注意循環(huán)引用删掀,引起內(nèi)存無法釋放,造成內(nèi)存泄漏导街。
AddSignHeaderView.h文件中定義block
@property (nonatomic, copy) void (^addMembersBtnOnClick)();
AddSignViewController.m文件中調用block
// 懶加載控件
- (AddSignHeaderView *)headerView {
if (!_headerView) {
_headerView = [[AddSignHeaderView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, 170)];
}
return _headerView;
}
// 調用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];
};
}
下面說一下披泪,為什么會引起循環(huán)引用搬瑰?
? ?從上面我們可以看到,controller引用了headerView卡乾,headerView里面擁有block屬性,在執(zhí)行block時候误堡,又引用了self,這就造成了循環(huán)引用,相互引用造成循環(huán)引用基跑,內(nèi)存泄漏,如下圖所示篱竭。
想解決循環(huán)引用的問題瓤介,就是打破這個引用循環(huán)氯质。將self進行弱化__weak typeof(self) weakSelf = self,如下圖所示呢灶。
二、assign
使用場景
- 在MRC 和 ARC下都可以使用。
- 一般用來修飾基礎數(shù)據(jù)類型(NSInteger, CGFloat) 和 C數(shù)據(jù)類型(int ,float, double)等宏蛉。它的setter方法直接賦值,不進行任何retain操作。
應用舉例
@property (nonatomic, assign) NSInteger studentNum;
@property (nonatomic, assign) CGFloat cellHeight;
注意事項
- MRC 和 ARC 都可以用assign之碗。
- assign下的setter方法:
-(void)setName :(id)str
{
name = str;
}
三、retain
使用場景
- 一般情況下博敬,retain用在MRC情況下,被retain修飾的對象,引用計數(shù)retainCount要加1的链沼。
- retain只能修飾oc對象曲掰,不能修飾非oc對象乱豆,比如說CoreFoundation對象就是C語言框架,它沒有引用計數(shù),也不能用retain進行修飾岩榆。
- retain一般用來修飾非NSString 的NSObject類和其子類折联。
應用舉例
@property (nonatomic, retain) DDDemoObject *modelObject;
注意事項
1. 要注意的就是循環(huán)引用造成的內(nèi)存泄漏,對于兩個對象A和B执赡,如果A對象中引用B對象,并且用retain修飾首懈;B對象中引用A對象脸狸,并且也用retain修飾泥彤。這個時候就是A和B相互引用,無法釋放浙宜,造成內(nèi)存泄漏。
如上圖所示鳖轰,A和B相互引用,造成A和B的引用計數(shù)都不為0辱志,無法釋放而留在內(nèi)存中已球,造成內(nèi)存泄漏点待,當這種內(nèi)存泄漏很嚴重時聋呢,會出現(xiàn)閃退等問題瓦呼。
解決辦法:將A和B其中的一端改成assign進行修飾碗啄,打斷這個循環(huán)引用的鏈,就解決了循環(huán)引用的問題。
如上圖所示昌讲,由于B引用A的時候用的是assign進行修飾车吹,那么A的引用計數(shù)可以為0乐埠,那么自然就解除了A對B的強引用洞拨,B的retainCount也可以為0烦衣,就解決了內(nèi)存泄漏的問題厨姚。
2. 下面說一下MRC下assign和retain的區(qū)別:assign只是簡單的賦值操 作拭抬,它引用的對象被釋放纷闺,會造成野指針,可能出現(xiàn)crash情況戒努;retain會使對象的retainCount計數(shù)加1储玫,獲得對象的擁有權,只有對象的引用計數(shù)為0的時候才會被釋放,避免訪問一個被釋放的對象蚀同。
3. retain下的setter方法
-(void) setName: (id) nameStr
{
if (name != nameStr) {
[name release];
name = [nameStr retain];
}
}
四娜睛、strong
使用場景
- strong表示對對象的強引用分预。
- ARC下也可以用來修飾block酪穿,strong 和 weak兩個修飾符默認是strong经磅。
- 用于指針變量元媚,setter方法對參數(shù)進行release舊值再retain新值。
應用舉例
@property (nonatomic, strong) NSArray *dataArr;
@property (nonatomic, strong) NSMutableArray *btnArray;
@property (nonatomic, strong) UILabel *descLabel;
// 對于控件也可以用weak屿笼,因為controller已經(jīng)對root view有一個強引用休雌,view addSubview 子控件担扑,所以即使用weak也不會提前釋放。
@property (nonatomic, strong) CompleteDatePicker *preciseDatePicker;
// CompleteDatePicker在這里是自定義類轴捎。
@property (nonatomic ,strong) NSString *signupId;
// 字符串除了用copy,用strong也是可以的穴张。
注意事項
- strong修飾的屬性璧瞬,對屬性進行的是強引用渔欢,對象的引用計數(shù)retainCount + 1九榔;
- 注意兩個對象之間相互強引用造成循環(huán)引用,內(nèi)存泄漏。
五就漾、weak
使用場景
- weak 表示對對象的弱引用呐能,被weak修飾的對象隨時可被系統(tǒng)銷毀和回收。
- weak比較常用的地方就是delegate屬性的設置抑堡。
- 用weak修飾弱引用摆出,不會使傳入對象的引用計數(shù)加1朗徊。
應用舉例
@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
// 上面自定義一個protocol DDCollegePickerVCDelegate,且在interface中將delegate屬性定義為weak偎漫,并且定義了一個inputView的控件荣倾。
注意事項
- 下面說一下前面所述的assign和weak的區(qū)別:當它們指向的對象釋放以后,weak會被自動設置為nil骑丸,而assign不會舌仍,所以會導致野指針的出現(xiàn),可能會導致crash通危。
- 下面說一下strong和weak的區(qū)別:
- strong :表明是一個強引用铸豁,相當于MRC下的retain,只要被strong引用的對象就不會被銷毀菊碟,當所有的強引用消除時节芥,對象的引用計數(shù)為0時,對象才會被銷毀逆害。
- weak : 表明是一個弱引用头镊,相當于MRC下的assign,不會使對象的引用計數(shù)+1魄幕。
- 兩個不同對象相互strong引用對象相艇,會導致循環(huán)引用造成對象不能釋放,造成內(nèi)存泄漏纯陨。
六坛芽、readwrite/readonly
使用場景
1. 當我們用readwrite修飾的時候表示該屬性可讀可改,用readonly修飾的時候表示這個屬性只可以讀取翼抠,不可以修改咙轩,一般常用在我們不希望外界改變只希望外界讀取這種情況。
2. readwrite 程序自動創(chuàng)建setter/getter方法阴颖,readonly 程序創(chuàng)建getter方法活喊。此外還可以自定義setter/getter方法。
3. 系統(tǒng)默認的情況就是 readwrite量愧。
應用舉例
1. 一般我們封裝屬性只希望外界能看到钾菊,自己能夠修改的時候,在.h文件里用readonly修飾侠畔,在.m文件里面用readwrite修飾结缚。
由上兩圖可知,m文件內(nèi)部readwrite修飾屬性cityName软棺,可以修改屬性值红竭,h文件暴露在外面的是onlyread屬性,這樣外面只能讀取不能修改該屬性值。
注意事項
1. 當希望外界能讀取我們這個屬性茵宪,但是不希望被外界改變的時候就用readonly最冰。
七、nonatomic/atomic
使用場景
1. nonatomic 非原子屬性稀火。它的特點是多線程并發(fā)訪問性能高暖哨,但是訪問不安全;與之相對的就是atomic凰狞,特點就是安全但是是以耗費系統(tǒng)資源為代價篇裁,所以一般在工程開發(fā)中用nonatomic的時候比較多。
2. 系統(tǒng)默認的是atomic赡若,為setter方法加鎖达布,而nonatomic 不為setter方法加鎖。
3. 如1所述逾冬,使用nonatomic要注意多線程間通信的線程安全黍聂。
應用舉例
// 這個例子就比較多了,基本上我們項目中都用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;
上面舉了幾個例子,實際上很多嘀趟,具體工程中都用nonatomic脐区,所以大家就不用糾結這兩個修飾符到底用哪一個了。
注意事項
1. 為了提高性能去件,一般我們就用nonatomic坡椒。所以對這個屬性修飾符我們可以不必過于糾結。
2. 注意atomic設置成員變量的@property屬性尤溜,提供多線程安全。在多線程中汗唱,原子操作是必須的宫莱。加入atomic屬性修飾符以后,setter函數(shù)會變成下面這樣:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
之所以這么做哩罪,是因為防止在寫未完成的時候被另外一個線程讀取授霸,造成數(shù)據(jù)錯誤。
3. 下面說一下為什么nonatomic要比atomic快际插。原因是:它直接訪問內(nèi)存中的地址碘耳,不關心其他線程是否在改變這個值,并且中間沒有死鎖保護框弛,它只需直接從內(nèi)存中訪問到當前內(nèi)存地址中能用到的數(shù)據(jù)即可(可以理解為getter方法一直可以返回數(shù)值辛辨,盡管這個數(shù)值在cpu中可能正在修改中)
4. 不要誤認為多線程下加atomic是安全的,這樣理解是不正確的,說明理解的不夠深入斗搞。atomic的安全只是在getter和setter方法的時候是原子操作指攒,是安全的。但是其他方面是不在atomic管理范圍之內(nèi)的僻焚,例如變量cnt的++運算允悦。這個時候不能保證安全。
@property int cnt;
@synthesize cnt = _cnt;
self.cnt = 0;
for (i = 0; i < n; i++) {
self.cnt ++;
}
這里線程就不是安全的虑啤,想要線程安全就得加鎖隙弛,加鎖的技術以后會專門寫一篇文章。
致謝
文章雖然很簡單狞山,但是時間倉促難免有所紕漏全闷,有錯誤的地方敬請指正,希望可以相互學習铣墨,共同進步室埋,同時謝謝朋友和同事的關心。