ios中屬性修飾符的作用

屬性修飾符簡述

ios5之前是MRC,內存需要程序員進行管理,ios5之后是ARC,除非特殊情況,比如C框架或者循環引用,其他時候是不需要程序員手動管理內存的。
? ios中當我們定義屬性@property的時候就需要屬性修飾符,下面我們就看一下不同屬性修飾符的作用。有錯誤和不足的地方還請大家諒解并批評指正。

主要的屬性修飾符有下面幾種:

  • copy
  • assign
  • retain
  • strong
  • weak
  • readwrite/readonly (讀寫策略、訪問權限)
  • nonatomic/atomic (安全策略)

如果以MRC和ARC進行區分修飾符使用情況,可以按照如下方式進行分組:

 1. MRC: assign/ retain/ copy/  readwrite、readonly/ nonatomic、atomic  等。
 2. ARC: assign/ strong/ weak/ copy/ readwrite、readonly/ nonatomic、atomic  等。

屬性修飾符對retainCount計數的影響。

  1. alloc為對象分配內存,retainCount 為1 。
  2. retain MRC下 retainCount + 1。
  3. copy 一個對象變成新的對象,retainCount為 1, 原有的對象計數不變。
  4. release 對象的引用計數 -1。
  5. autorelease 對象的引用計數 retainCount - 1,如果為0,等到最近一個pool結束時釋放。

不管MRC還是ARC,其實都是看reference count是否為0,如果為0那么該對象就被釋放,不同的地方是MRC需要程序員自己主動去添加retain 和 release,而ARC apple已經給大家做好,自動的在合適的地方插入retain 和 release類似的內存管理代碼,具體原理如下,圖片摘自官方文檔。

MRC 和 ARC原理

下面就詳述上所列的幾種屬性修飾符的使用場景,應用舉例和注意事項。

屬性修飾符詳述

一、copy

使用場景

  1. 一般情況下,copy可以用于對不可變容易的屬性修飾中,主要是NSArray /NSDictionary/NSString, 也可以用來修飾block。
  2. 在MRC和ARC下都可以使用。
  3. 其setter方法,與retain處理流程一樣,先舊值release,再copy出新的對象。

應用舉例

@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) void(^typeBlock)(BOOL selected);
@property (nonatomic, copy) void(^cancelBlock)();

注意事項

  1. 要注意的就是深淺拷貝,這個也是使用copy這個屬性修飾符最重要的地方,這以后會在另一篇文章里面單獨講。
  2. MRC 和 ARC 都可以用copy。
  3. copy下的setter方法。
-(void)setName: (id)newName {
      if (name != newName) {
        [name release];
        name = [newName copy];
     }
}
  1. 用copy修飾block時在MRC和ARC下的區別
  • MRC環境下
    (1)block訪問外部局部變量,block存放在棧里面。
    (2)只要block訪問整個app都存在的變量,那么肯定是在全局區。
    (3)不能使用retain引用block,因為block不在堆區里面,只有使用copy才會把block放在堆區里面。

  • ARC環境下
    (1)只要block訪問外部局部變量,block就會存放在堆區。
    (2)可以使用strong去引用,因為本身就已經存放在堆區了。
    (3)也可以使用copy進行修飾,但是strong性能更好。

  1. 當使用block的時候注意循環引用,引起內存無法釋放,造成內存泄漏。

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 = @"選擇聯系人";
        addSign.delegate = weakSelf;
        [weakSelf.navigationController pushViewController:addSign animated:YES];
    };
}

下面說一下,為什么會引起循環引用?
? ?從上面我們可以看到,controller引用了headerView,headerView里面擁有block屬性,在執行block時候,又引用了self,這就造成了循環引用,相互引用造成循環引用,內存泄漏,如下圖所示。

block造成循環引用

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

打破block的循環引用

二、assign

使用場景

  1. 在MRC 和 ARC下都可以使用。
  2. 一般用來修飾基礎數據類型(NSInteger, CGFloat) 和 C數據類型(int ,float, double)等。它的setter方法直接賦值,不進行任何retain操作。

應用舉例

@property (nonatomic, assign) NSInteger  studentNum;
@property (nonatomic, assign) CGFloat  cellHeight;

注意事項

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

三、retain

使用場景

  1. 一般情況下,retain用在MRC情況下,被retain修飾的對象,引用計數retainCount要加1的。
  2. retain只能修飾oc對象,不能修飾非oc對象,比如說CoreFoundation對象就是C語言框架,它沒有引用計數,也不能用retain進行修飾。
  3. retain一般用來修飾非NSString 的NSObject類和其子類。

應用舉例

@property (nonatomic, retain) DDDemoObject *modelObject;

注意事項

1. 要注意的就是循環引用造成的內存泄漏,對于兩個對象A和B,如果A對象中引用B對象,并且用retain修飾;B對象中引用A對象,并且也用retain修飾。這個時候就是A和B相互引用,無法釋放,造成內存泄漏。

retain MRC循環引用

如上圖所示,A和B相互引用,造成A和B的引用計數都不為0,無法釋放而留在內存中,造成內存泄漏,當這種內存泄漏很嚴重時,會出現閃退等問題。
解決辦法:將A和B其中的一端改成assign進行修飾,打斷這個循環引用的鏈,就解決了循環引用的問題。

retain/assign MRC下解除循環引用

如上圖所示,由于B引用A的時候用的是assign進行修飾,那么A的引用計數可以為0,那么自然就解除了A對B的強引用,B的retainCount也可以為0,就解決了內存泄漏的問題。

2. 下面說一下MRC下assign和retain的區別:assign只是簡單的賦值操 作,它引用的對象被釋放,會造成野指針,可能出現crash情況;retain會使對象的retainCount計數加1,獲得對象的擁有權,只有對象的引用計數為0的時候才會被釋放,避免訪問一個被釋放的對象。

3. retain下的setter方法

-(void) setName: (id) nameStr
{
      if (name != nameStr) {
        [name release];
        name = [nameStr retain];
     }
}

四、strong

使用場景

  1. strong表示對對象的強引用。
  2. ARC下也可以用來修飾block,strong 和 weak兩個修飾符默認是strong。
  3. 用于指針變量,setter方法對參數進行release舊值再retain新值。

應用舉例

@property (nonatomic, strong) NSArray  *dataArr;
@property (nonatomic, strong) NSMutableArray *btnArray;
@property (nonatomic, strong) UILabel *descLabel;
  //  對于控件也可以用weak,因為controller已經對root view有一個強引用,view addSubview 子控件,所以即使用weak也不會提前釋放。
@property (nonatomic, strong) CompleteDatePicker *preciseDatePicker;
 //  CompleteDatePicker在這里是自定義類。
@property (nonatomic ,strong) NSString *signupId;
 // 字符串除了用copy,用strong也是可以的。


注意事項

  1. strong修飾的屬性,對屬性進行的是強引用,對象的引用計數retainCount + 1;
  2. 注意兩個對象之間相互強引用造成循環引用,內存泄漏。

五、weak

使用場景

  1. weak 表示對對象的弱引用,被weak修飾的對象隨時可被系統銷毀和回收。
  2. weak比較常用的地方就是delegate屬性的設置。
  3. 用weak修飾弱引用,不會使傳入對象的引用計數加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的控件。

注意事項

  1. 下面說一下前面所述的assign和weak的區別:當它們指向的對象釋放以后,weak會被自動設置為nil,而assign不會,所以會導致野指針的出現,可能會導致crash。
  2. 下面說一下strong和weak的區別:
  • strong :表明是一個強引用,相當于MRC下的retain,只要被strong引用的對象就不會被銷毀,當所有的強引用消除時,對象的引用計數為0時,對象才會被銷毀。
  • weak : 表明是一個弱引用,相當于MRC下的assign,不會使對象的引用計數+1。
  1. 兩個不同對象相互strong引用對象,會導致循環引用造成對象不能釋放,造成內存泄漏。

六、readwrite/readonly

使用場景

1. 當我們用readwrite修飾的時候表示該屬性可讀可改,用readonly修飾的時候表示這個屬性只可以讀取,不可以修改,一般常用在我們不希望外界改變只希望外界讀取這種情況。
2. readwrite 程序自動創建setter/getter方法,readonly 程序創建getter方法。此外還可以自定義setter/getter方法。
3. 系統默認的情況就是 readwrite。

應用舉例

1. 一般我們封裝屬性只希望外界能看到,自己能夠修改的時候,在.h文件里用readonly修飾,在.m文件里面用readwrite修飾。

h文件readonly
m文件readwrite

由上兩圖可知,m文件內部readwrite修飾屬性cityName,可以修改屬性值,h文件暴露在外面的是onlyread屬性,這樣外面只能讀取不能修改該屬性值。

注意事項

1. 當希望外界能讀取我們這個屬性,但是不希望被外界改變的時候就用readonly。


七、nonatomic/atomic

使用場景

1. nonatomic 非原子屬性。它的特點是多線程并發訪問性能高,但是訪問不安全;與之相對的就是atomic,特點就是安全但是是以耗費系統資源為代價,所以一般在工程開發中用nonatomic的時候比較多。
2. 系統默認的是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函數會變成下面這樣:

{lock}
    if (property != newValue) {
         [property release];
         property = [newValue retain];
     }
{unlock}

之所以這么做,是因為防止在寫未完成的時候被另外一個線程讀取,造成數據錯誤。
3. 下面說一下為什么nonatomic要比atomic快。原因是:它直接訪問內存中的地址,不關心其他線程是否在改變這個值,并且中間沒有死鎖保護,它只需直接從內存中訪問到當前內存地址中能用到的數據即可(可以理解為getter方法一直可以返回數值,盡管這個數值在cpu中可能正在修改中)
4. 不要誤認為多線程下加atomic是安全的,這樣理解是不正確的,說明理解的不夠深入。atomic的安全只是在getter和setter方法的時候是原子操作,是安全的。但是其他方面是不在atomic管理范圍之內的,例如變量cnt的++運算。這個時候不能保證安全。

@property  int cnt;
    @synthesize cnt = _cnt;
    self.cnt = 0;
    for (i = 0; i < n; i++) {
      self.cnt ++;
    }

這里線程就不是安全的,想要線程安全就得加鎖,加鎖的技術以后會專門寫一篇文章。


致謝

文章雖然很簡單,但是時間倉促難免有所紕漏,有錯誤的地方敬請指正,希望可以相互學習,共同進步,同時謝謝朋友和同事的關心。

參考資料和相關博客

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容