ios中屬性修飾符的作用

屬性修飾符簡(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ù)的影響。

  1. alloc為對(duì)象分配內(nèi)存,retainCount 為1 。
  2. retain MRC下 retainCount + 1。
  3. copy 一個(gè)對(duì)象變成新的對(duì)象,retainCount為 1, 原有的對(duì)象計(jì)數(shù)不變。
  4. release 對(duì)象的引用計(jì)數(shù) -1。
  5. 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)存管理代碼,具體原理如下,圖片摘自官方文檔。

MRC 和 ARC原理

下面就詳述上所列的幾種屬性修飾符的使用場(chǎng)景,應(yīng)用舉例和注意事項(xiàng)。

屬性修飾符詳述

一、copy

使用場(chǎng)景

  1. 一般情況下,copy可以用于對(duì)不可變?nèi)菀椎膶傩孕揎椫?,主要是NSArray /NSDictionary/NSString, 也可以用來(lái)修飾block。
  2. 在MRC和ARC下都可以使用。
  3. 其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)

  1. 要注意的就是深淺拷貝,這個(gè)也是使用copy這個(gè)屬性修飾符最重要的地方,這以后會(huì)在另一篇文章里面單獨(dú)講。
  2. MRC 和 ARC 都可以用copy。
  3. copy下的setter方法。
-(void)setName: (id)newName {
      if (name != newName) {
        [name release];
        name = [newName copy];
     }
}
  1. 用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性能更好。

  1. 當(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)存泄漏,如下圖所示。

block造成循環(huán)引用

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

打破block的循環(huán)引用

二、assign

使用場(chǎng)景

  1. 在MRC 和 ARC下都可以使用。
  2. 一般用來(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)

  1. MRC 和 ARC 都可以用assign。
  2. assign下的setter方法:
-(void)setName :(id)str
{
        name = str;
}

三、retain

使用場(chǎng)景

  1. 一般情況下,retain用在MRC情況下,被retain修飾的對(duì)象,引用計(jì)數(shù)retainCount要加1的。
  2. retain只能修飾oc對(duì)象,不能修飾非oc對(duì)象,比如說(shuō)CoreFoundation對(duì)象就是C語(yǔ)言框架,它沒(méi)有引用計(jì)數(shù),也不能用retain進(jìn)行修飾。
  3. 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)存泄漏。

retain MRC循環(huán)引用

如上圖所示,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)題。

retain/assign MRC下解除循環(huá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)景

  1. strong表示對(duì)對(duì)象的強(qiáng)引用。
  2. ARC下也可以用來(lái)修飾block,strong 和 weak兩個(gè)修飾符默認(rèn)是strong。
  3. 用于指針變量,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)

  1. strong修飾的屬性,對(duì)屬性進(jìn)行的是強(qiáng)引用,對(duì)象的引用計(jì)數(shù)retainCount + 1;
  2. 注意兩個(gè)對(duì)象之間相互強(qiáng)引用造成循環(huán)引用,內(nèi)存泄漏。

五、weak

使用場(chǎng)景

  1. weak 表示對(duì)對(duì)象的弱引用,被weak修飾的對(duì)象隨時(shí)可被系統(tǒng)銷毀和回收。
  2. weak比較常用的地方就是delegate屬性的設(shè)置。
  3. 用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)

  1. 下面說(shuō)一下前面所述的assign和weak的區(qū)別:當(dāng)它們指向的對(duì)象釋放以后,weak會(huì)被自動(dòng)設(shè)置為nil,而assign不會(huì),所以會(huì)導(dǎo)致野指針的出現(xiàn),可能會(huì)導(dǎo)致crash。
  2. 下面說(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。
  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修飾。

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)心。

參考資料和相關(guān)博客

  1. Objective-C——retain/copy/assign/atomic/nonatomic/strong/weak/readonly/readwrite詳解

  2. 整理一下OC中的那些屬性修飾符 --iOS小孟和小夢(mèng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容