【iOS】定義@property時常用的修飾詞介紹

淺淺青丘發表於2018-06-13

iOS程式設計中,定義成員變數常用格式如下:

@property (nonatomic, strong) UILabel *label; 

常用的修飾詞有atomic,nonatomic,copy,assign,strong,weak,readonly,readwrite等。面試中也會常常被問到這些修飾詞的含義及其之間的區別,其本質就是iOS的記憶體管理,下面會詳細介紹每個詞的特性和它們的區別,還會補充__block與__weak的相關知識。

atomic

  • 屬性的預設屬性,當前執行緒進行到一半其他執行緒來訪問當前執行緒,可以保證先執行完畢當前執行緒,但只是保證setter/getter完整,不是執行緒安全的。
  • atomic雖然對屬性的讀和寫是原子性的,但是仍然可能出現執行緒錯誤,比如:@property(atomic,strong)NSMutableArray *mArray;如果一個執行緒對mArray迴圈讀資料,另一個執行緒迴圈寫資料,那麼[self.mArray objectAtIndex:index]就不是執行緒安全的。可以用加鎖的方法來解決這個執行緒不安全的問題。
  • atomic的作用只是給getter和setter加了個鎖,atomic只能保證程式碼進入getter或者setter函式內部時是安全的,一旦出了getter和setter,多執行緒安全只能靠程式設計師自己保障了。所以atomic屬性和使用property的多執行緒安全並沒什麼直接的聯絡。另外,atomic由於加鎖也會帶來一些效能損耗,所以我們在編寫iOS程式碼的時候,一般宣告property為nonatomic,在需要做多執行緒安全的場景,自己去額外加鎖做同步。

    nonatomic

  • 屬性的非預設屬性,需要編碼者手動寫。不僅是執行緒不安全的,也無法保證setter/getter的完整,多個執行緒同時訪問這個屬性,結果無法預計。
  • nonatomic速度比atomic速度快,所以通常用nonatomic。

    copy

  • 常見的copy宣告的是NSString和block。
  • copy生成的物件是一個新的物件,不是引用原來的物件,對原物件的引用計數無影響。
  • 使用copy建立的新物件也是強引用,使用完成後需要負責釋放該物件。
  • copy特性可以減少物件對上下文的依賴。新物件、原始物件中任一物件的值的改變不會影響另一個物件的值。
  • 以下程式碼可驗證copy修飾(NSString的實現遵循了NSCopy協議,所以預設為copy)的各個物件值的改變互不影響。

    -(void)stringCopyTest{
    NSString * A = @"A";
    NSString * B = A;
    NSString * C = [A copy];
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    A = @"AB";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    B = @"ABC";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    C = @"abcd";
    NSLog(@"A === %@ B === %@ C === %@",A,B,C);
    }

    assign

  • 只是引用,只返回指標。用於float、int等基本資料型別。
  • 如果用來修飾物件,編譯會產生警告:Assigning retained object to unsafe property; object will be released after assignment。在釋放之後,指標的地址還存在,也就是說指標並沒有被置為nil,會造成野指標。物件一般分配在堆上的某塊記憶體,如果後續的記憶體分配中用到了這塊地址,就會造成crash。
  • 由於基本資料型別一般是分配在棧上的,棧的記憶體會由系統自己處理,不會造成野指標,所以assign可以用來修飾基本資料型別。

    strong

  • 屬性的預設屬性,iOS引入ARC之後,用strong代替了retain。
  • 建立一個強引用的指標,引用物件的引用計數加1。strong特性表示兩個物件記憶體地址相同(建立一個指標,進行指標拷貝),內容會一直保持相同,直到更改一方的記憶體地址,或者將其置為nil。
  • 除了NSString之外的例項變數和區域性變數都預設是strong。
  • 以下程式碼可以驗證如果有多個strong物件同時引用一個屬性,任一物件對該屬性的修改都會影響其他物件獲取的值。

    -(void)strongTest{
    NSMutableArray * marray1 = [NSMutableArray arrayWithArray:@[@"aaa"]];
    NSMutableArray * array = marray1;
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    [marray1 addObject:@"bbb"];
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    [array addObject:@"ccc"];
    NSLog(@"1 === %@ 2 === %@",marray1,array);
    }

    weak

  • 弱引用,物件引用技術不會加1。weak修飾的物件被釋放之後,會自動置為nil,像nil傳送訊息,什麼都不會執行,程式也不會crash。
  • 代理使用weak。delegate幾乎一直持有代理物件,所以代理物件應該對代理使用weak,否則會形成迴圈引用。但也有例外,如果代理物件的生命週期比較短,代理物件也可以使用strong。

    readonly

  • 變數的非預設屬性,只有可讀方法,即只有getter方法。

    readwrite

  • 變數的預設屬性,會生成setter方法和getter方法。
  • 如果希望一個屬性只允許自己讀寫,而對所有外部檔案都是隻讀的,可以在介面部分宣告該屬性為readonly型別,最後在私有介面部分重寫該屬性為readwrite型別。

    assign 與weak

  • assign適用於基本資料型別,weak適用於NSObject物件,並且是一個弱引用。
  • assign修飾的物件被釋放後記憶體地址不會被釋放,會造成野指標;weak修飾的物件被釋放後,物件會被置為nil,不會出現記憶體洩漏的問題。

    strong 與copy

  • 通過上面的程式碼可能看出strong的兩個指標會指向同一個記憶體地址;copy會在記憶體裡拷貝一份物件,兩個指標指向不同的記憶體地址。
  • block的迴圈引用並不是strong導致的,在ARC下,系統底層也會做一次copy操作使block從棧區複製一塊記憶體空間到堆區,所以strong和copy在對block的修飾上是沒有本質區別。

    strong與weak

  • strong是強引用,會讓物件的引用計數+1;weak是弱引用,物件的引用計數不變。
  • 幫助理解二者區別的例子:假設物件是一條小狗,小狗想跑。strong型別就像是拴狗的繩子,只要有一條繩子栓住狗,它就不能跑走,如果有五條繩子拴著同一條狗(五個strong型別指向同一個物件),只有當五條繩子都釋放狗才可以跑走。weak型別就像是小孩子看著小狗說:看這裡有小狗。只要還有繩子拴著小狗,小孩子們就可以繼續指著小狗說:看這裡有小狗。當繩子釋放了的時候,不管有多少小孩子依舊在指著小狗說:看這裡的小狗。小狗都會跑掉。當最後一個strong指標不再指向這個物件,這個物件就會被釋放,此時,所有指向這個物件的weak指標都將被清空且置為nil。
    # __block與__weak
  • __block是用來修飾一個變數,這個變數可以在block中被修改。
  • 使用__block修飾的變數在block程式碼塊中會被retain(ARC下會retain,MRC下不會retain)。
  • 使用__weak修飾的變數不會在block程式碼塊中被retain。同時,在ARC下,要避免出現迴圈引用可以使用如下程式碼:

    __weak typedof(self) weakSelf = self;

    整理這些的時候也寫了一些程式碼來驗證,發現東西其實很多,往細處想只會發現自己不會的更多。面試的時候經常會聽到的一句話就是說一下記憶體管理,以前都會很懵,感覺記憶體管理無從說起。現在慢慢理解,其實這些東西都是記憶體管理的體現,再遇到問這個問題的,這些都能說上來應該也不算太差。


相關文章