iOS @property及其關鍵字學習記錄

即將成為型男的濤發表於2019-04-03

前言

iOS @property及其關鍵字學習記錄

一、@property的本質

當我們寫下@property NSObject *name時,編譯器幫我們做了以下幾件事:

  • 建立例項變數_name
  • 宣告name屬性的setter、getter方法
  • 實現name屬性的setter、getter方法

所以我們可以看出@property的本質是:

@property = ivar + getter + setter;
複製程式碼

也就是說使用@property時, 系統會自動建立例項變數及其setter和getter方法;


二、@property的常見關鍵字

讀寫許可權

  • readwrite(可讀可寫),系統預設的屬性。

  • readonly(只讀),當宣告瞭這個屬性,系統不會為其生成getter方法,當你希望暴露出來的屬性不能被外界修改時使用。

原子性

  • atomic 和 nonatomic 用來決定編譯器生成的getter和setter是否為原子操作。

  • atomic: 會保證系統生成的 getter/setter 操作的完整性,不受其他執行緒影響。getter 還是能得到一個完好無損的物件(可以保證資料的完整性),但這個物件在多執行緒的情況下是不能確定的。舉例如下:

@interface ViewController ()
@property(atomic, strong) NSString *testStr;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.testStr = @"JiangT";
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // getter
    NSBlockOperation *oper1 =[NSBlockOperation blockOperationWithBlock:^{
        NSLog(self.testStr);
    }];
    
    // setter
    NSBlockOperation *oper2 = [NSBlockOperation blockOperationWithBlock:^{
        self.testStr = @"change one";
    }];
    
    // setter
    NSBlockOperation *oper3 = [NSBlockOperation blockOperationWithBlock:^{
        self.testStr = @"change two";
    }];
    
    // setter
    NSBlockOperation *oper4 = [NSBlockOperation blockOperationWithBlock:^{
        self.testStr = @"change three";
    }];
    
    // setter
    NSBlockOperation *oper5 = [NSBlockOperation blockOperationWithBlock:^{
        self.testStr = @"change four";
    }];
    
    //建立佇列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[oper1, oper2, oper3, oper4, oper5] waitUntilFinished:NO];
}
@end

---輸出結果:---
AtomicDemo[52660:1647356] change two
AtomicDemo[52660:1647356] change four
AtomicDemo[52660:1647356] change four
AtomicDemo[52660:1647354] change two
AtomicDemo[52660:1647354] change four
AtomicDemo[52660:1647354] change four
AtomicDemo[52660:1647356] change four
複製程式碼

如果執行緒 A 調了 getter,與此同時執行緒 B 、執行緒 C 都調了 setter。那最後執行緒 A get 到的值,有3種 可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同時,最終這個屬性的值,可能是 B set 的值,也有可能是 C set 的值。所以atomic可並不能保證物件的執行緒安全。

也就是說:如果有多個執行緒同時呼叫setter的話,不會出現某一個執行緒執行完setter全部語句之前,另一個執行緒開始執行setter情況,相當於函式頭尾加了鎖一樣,每次只能有一個執行緒呼叫物件的setter方法,所以可以保證資料的完整性。

結論: atomic所說的執行緒安全只是保證了getter和setter存取方法的執行緒安全,並不能保證整個物件是執行緒安全的。

  • nonatomic:就沒有這個保證了,nonatomic返回你的物件可能就不是完整的value。因此,在多執行緒的環境下原子操作是非常必要的,否則有可能會引起錯誤的結果。但僅僅使用atomic並不會使得物件執行緒安全,我們還要為物件執行緒新增lock來確保執行緒的安全。

  • nonatomic的速度要比atomic的快。atomic是Objc使用的一種執行緒保護技術,這種機制是耗費系統資源的,所以在iPhone這種小型裝置上,我們基本上都是使用nonatomic,而物件的執行緒安全問題則由程式設計師程式碼控制。

引用計數

strong 及 retain

  • strong

    • 強引用,其存亡直接決定了所指向物件的存亡。使用該特性例項變數在賦值時,會釋放舊值同時設定新值,對物件產生一個強引用,即引用計數+1。如果不存在指向一個物件的引用,並且此物件不再顯示在列表中,則此物件會被從記憶體中釋放。適用於一般OC物件。
  • retaine

    • 一般情況下等同於ARC環境下的strong修飾符,但是在修飾block物件時,retain相當於assign,而strong相當於copy。
    • 生成符合記憶體管理的set方法(release舊值,retain新值),適用於OC物件的成員變數。

assign 及 unsafe_unretained

永遠指向某記憶體地址,如果該記憶體被釋放了,自己就會成為野指標。

  • assign

    • 這個修飾詞是直接賦值的意思 , 整型/浮點型等資料型別都用這個詞修飾。
    • 如果沒有使用 weak strong retain copy 修飾 , 那麼預設就是使用 assign 了。
    • 修飾物件型別時,不改變其引用計數。
    • 如果用來修飾物件屬性 , 那麼當物件被銷燬後指標是不會指向 nil 的。 所以會出現野指標錯誤。
  • unsafe_unretained

    • unsafe_unretained與assign類似,但是用於物件型別,從字面意思上,也能看到,它是不安全,也不會強引用物件,所以它跟weak很相似,跟weak的區別在於當指向的物件被釋放時,屬性不會被置為nil,所以是不安全的。

copy

淺拷貝:只是將物件記憶體地址多了一個引用,也就是說,拷貝結束之後,兩個物件的值不僅相同,而且物件所指的記憶體地址都是一樣的。

深拷貝:拷貝一個物件的具體內容,拷貝結束之後,兩個物件的值雖然是相同的,但是指向的記憶體地址是不同的。兩個物件之間也互不影響,互不干擾。

  • 在非集合類物件中,對不可變物件進行copy操作,只僅僅是指標複製——淺複製,進行mutableCopy操作,是內容複製——深複製。

  • 對於不可變的集合類物件進行copy操作,只是改變了指標,其記憶體地址並沒有發生變化;進行mutableCopy操作,記憶體地址發生了變化,但是其中的元素記憶體地址並沒有發生變化。

  • 對於可變集合類物件,不管是進行copy操作還是mutableCopy操作,其記憶體地址都發生了變化,但是其中的元素記憶體地址都沒有發生變化,屬於單層深拷貝。

使用注意:

  1. 當將一個可變物件分別賦值給兩個使用不同修飾詞的屬性後,改變可變物件的內容,使用strong修飾的會跟隨著改變,但使用copy修飾的沒有改變內容。
@interface test()
 
@property(nonatomic, strong) NSMutableString *strStrong;
@property(nonatomic, copy) NSMutableString *strCopy;
 
@end
 
/********************* test.m **********************/
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];

self.strStrong = str;    
self.strCopy = str;
    
[self.strStrong appendString:@"def"];
[self.strCopy appendString:@"def"];// 在這一行會crash
複製程式碼
iOS @property及其關鍵字學習記錄

因為copy是複製出一個不可變的物件,在不可變物件上執行可變物件的方法,就會找不到執行方法。

weak

1、作用
  • weak 必須用於 OC 物件
  • 表示的是一個弱引用,這個引用不會增加物件的引用計數。
  • 在所指向的物件被釋放之後,weak指標會被置為nil。
  • 用於解決迴圈引用。例如:delegate屬性常用weak修飾。
2、原理

Runtime維護了一個weak表,用於儲存指向某個物件的所有weak指標。weak表其實是一個hash表,K ey是所指物件的地址,value是weak指標的地址(這個地址的值是所指物件指標的地址)陣列。

為什麼value是陣列?因為一個物件可能被多個弱引用指標指向。

iOS @property及其關鍵字學習記錄

  1. 初始化時:

    runtime會呼叫objc_initWeak函式,objc_initWeak函式會初始化一個新的weak指標指向物件的地址。

  2. 新增引用時:

    objc_initWeak函式會呼叫 objc_storeWeak() 函式, objc_storeWeak() 的作用是更新指標指向,建立對應的弱引用表。

  3. 釋放時:

    呼叫clearDeallocating函式。clearDeallocating函式首先根據物件地址獲取所有weak指標地址的陣列,然後遍歷這個陣列把其中的資料設為nil,最後把這個entry從weak表中刪除,最後清理物件的記錄。

思考題:IBOutlet連出來的檢視屬性為什麼可以被設定成weak?

因為父控制元件的subViews陣列已經對它有一個強引用。

所以:當自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak。

相關文章