iOS @property 屬性相關的總結

orilme發表於2019-04-16

@property 常用屬性

讀寫屬性:readwritereadonly
setter語意:assignretain / copy
原子性(多執行緒管理):atomicnonatomic
強弱引用:strongweak

  • 讀寫屬性:
    readwrite :同時生成 setget 方法(預設)
    readonly :只會生成 get 方法

  • 控制set方法的記憶體管理:
    retainrelease 舊值,retain 新值。希望獲得源物件的所有權時,對其他 NSObject 和其子類(用於 OC 物件)
    copyrelease 舊值,copy 新值。希望獲得源物件的副本而不改變源物件內容時(一般用於 NSStringblock )
    assign :直接賦值,不做任何記憶體管理(預設屬性),控制需不需生成 set 方法。對基礎資料型別 (NSIntegerCGFloat )和C資料型別(int , float , double , char , 等等),另外還有id型別

  • 原子性(多執行緒管理):

    • atomic
      預設屬性,訪問方法都為原子型事務訪問。鎖被加到所屬物件例項級,效能低。原子性就是說一個操作不可以中途被 cpu 暫停然後排程, 即不能被中斷, 要不就執行完, 要不就不執行. 如果一個操作是原子性的,那麼在多執行緒環境下, 就不會出現變數被修改等奇怪的問題。原子操作就是不可再分的操作,在多執行緒程式中原子操作是一個非常重要的概念,它常常用來實現一些同步機制,同時也是一些常見的多執行緒 Bug 的源頭。當然,原子性的變數在執行效率上要低些。
    • nonatomic
      非原子性訪問。不加同步,儘量避免多執行緒搶奪同一塊資源。是直接從記憶體中取數值,因為它是從記憶體中取得資料,它並沒有一個加鎖的保護來用於cpu中的暫存器計算Value,它只是單純的從記憶體地址中,當前的記憶體儲存的資料結果來進行使用。 多執行緒併發訪問會提高效能,但無法保證資料同步。儘量避免多執行緒搶奪同一塊資源,否則儘量將加鎖資源搶奪的業務邏輯交給伺服器處理,減少移動客戶端的壓力。
      當有多個執行緒需要訪問到同一個資料時,OC中,我們可以使用 @synchronized (變數)來對該變數進行加鎖(加鎖的目的常常是為了同步或保證原子操作)。
  • 強指標(strong)、弱指標(weak)

    • strong
      strong 系統一般不會自動釋放,在 oc 中,物件預設為強指標。作用域銷燬時銷燬引用。在實際開放中一般屬性物件一般 strong 來修飾(NSArrayNSDictionary),在使用懶載入定義控制元件的時候,一般也用strong。
    • weak
      weak 所引用物件的計數器不會加一,當物件被釋放時指標會被自動賦值為 nil,系統會立刻釋放物件。
    • __unsafe_unretained 弱引用 當物件被釋放時指標不會被自動賦值為 ni
      在ARC時屬性的修飾符是可以用 assign 的(相當於 __unsafe_unretained
      在ARC時屬性的修飾符是可以用 retain 的 (相當於 __strong)
    • 假定有N個指標指向同一個物件,如果至少有一個是強引用,這個物件只要還在作用域內就不會被釋放。相反,如果這N個指標都是弱引用,這個物件馬上就被釋放
    • 在使用 sb 或者 xib 給控制元件拖線的時候,為什麼拖出來的先屬性都是用 weak 修飾呢?
      由於在向 xib 或者 sb 裡面新增控制元件的時候,新增的子檢視是新增到了跟檢視 View 上面,而 控制器 Controller 對其根檢視 View 預設是強引用的,當我們的子控制元件新增到 view 上面的時候,self.view addSubView: 這個方法會對新增的控制元件進行強引用,如果在用 strong 對新增的子控制元件進行修飾的話,相當於有兩條強指標對子控制元件進行強引用, 為了避免這種情況,所以用 weak 修飾。
      注意:
      (1)addSubView 預設對其 subView 進行了強引用
      (2)在純手碼實現介面佈局時,如果通過懶載入處理介面控制元件,需要使用strong強指標
  • ARC管理記憶體是用 assign 還是用 weak
    assign : 如果由於某些原因代理物件被釋放了,代理指標就變成了野指標。
    weak : 如果由於某些原因代理物件被釋放了,代理指標就變成了空指標,更安全(weak 不能修飾基本資料型別,只能修飾物件)。

weak修飾符

  • weak的作用:
    weak 關鍵字的作用弱引用,所引用物件的計數器不會加一,並在引用物件被釋放的時候自動被設定為 nil,大大避免了野指標訪問壞記憶體引起崩潰的情況,另外 weak 還可以用於解決迴圈引用。

  • 使用場景:
    用於一些物件相互引用的時候,避免出現強強引用,物件不能被釋放,出現記憶體洩露的問題。

  • 實現原理:
    runtime 維護了一個 weak 表,用於儲存指向某個物件的所有 weak 指標。weak 表其實是一個hash(雜湊)表,Key 是所指物件的地址,Valueweak 指標的地址(這個地址的值是所指物件的地址)陣列。(備註:strong 是通過 runtime 維護的一個自動計數表結構)
    weak 的實現原理可概括三步:

    1. 初始化時:runtime 會呼叫 objc_initWeak 函式,初始化一個新的 weak 指標指向物件的地址。
    2. 新增引用時:objc_initWeak 函式會呼叫 objc_storeWeak() 函式, objc_storeWeak() 的作用是更新指標指向,建立對應的弱引用表。
    3. 釋放時,呼叫 clearDeallocating 函式。clearDeallocating 函式首先根據物件地址獲取所有 weak 指標地址的陣列,然後遍歷這個陣列把其中的資料設為 nil ,最後把這個 entryweak 表中刪除,最後清理物件的記錄。
  • 文章推薦:
    iOS 底層解析weak的實現原理(包含weak物件的初始化,引用,釋放的分析)
    淺談iOS之weak底層實現原理

深淺複製

深複製與淺複製

  • 深複製
    • 源物件和副本物件不同的各兩個物件
    • 源物件的引用計數器不變,副本物件的引用計數器為 1(因為是新產生的)
    • 本質產生了新的物件
  • 淺複製
    • 源物件和副本物件是一個物件
    • 源物件(副本物件)引用計數器 + 1,相當於做了一次 retain 操作
    • 本質沒有產生新的物件

複製與計數器

  • 深複製
// 深複製:產生了新物件,新物件(副本物件)的計數器是1, 源物件的計數器不變
// str : 1
NSString *str = [NSString stringWithFormat:@"Tom"];
// str2 : 1
NSMutableString *str2 = [str mutableCopy];     
NSLog(@"str=%zd, str2=%zd", [str retainCount], [str2 retainCount]);
[str2 release];
複製程式碼
  • 淺複製
// 淺複製:沒有產出新物件, 源物件(副本物件)的計數器會+1
NSString *str = [NSString stringWithFormat:@"Jack"];
NSString *str2 = [str copy];
[str2 release];
NSLog(@"%zd", [str retainCount]);
複製程式碼

copy 與 mutableCopy

copy 產生不可變副本,mutableCopy 產生可變副本

copy與mutableCopy.png
理論上講OC中的任何一個物件都可以複製(copymutableCopy ),只是Foundation庫中的類的物件可以直接複製,自定義的類的物件需要做一些額外的工作才能複製,但實際做app幾乎不需要複製自定義類的物件。

不可變物件和可變物件的 copymutableCopy 對比

不可變物件 copy不可變物件 是淺複製,其他都是深複製。(不可變物件指NSArray、NSDictionary、NSString等)

可變物件/不可變物件的copy/mutableCopy.png

copy 與 strong

NSMutableArraycopystrong 修飾後的變化 把NSMutableArray用copy修飾有時就會crash,因為對這個陣列進行了增刪改操作,而copy後的陣列變成了不可變陣列NSArray,沒有響應的增刪改方法,所以對其進行增刪改操作就會報錯。 舉例如下:

NSMutableArray *b = [NSMutableArray array];
a = b;
複製程式碼
  • @property (nonatomic, copy) NSMutableArray *a;
NSMutableArray *b = [NSMutableArray array];
// a被copy後就成了NSArray了。
a = [b copy];
複製程式碼
  • @property (nonatomic, strong) NSMutableArray *a; 如果是 strong,直接是賦值 a = b;右邊是什麼,左邊就是什麼,並且是強引用新值,左邊的型別會與右邊的相同,不會改變。

文章推薦

iOS 深拷貝淺拷貝與@property 引用計數關鍵字Strong,Copy,Weak,Assign
iOS 淺談:深.淺拷貝與copy.strong
陣列的屬性修飾符到底用strong還是copy?
iOS中copy和mutableCopy的詳細分析

NSString 為什麼用 copy 而不用 retain

我們通過實際操作來說明,我們把str賦值給 zhangsanname 屬性,然後去改變 str,結果: 用 @property (nonatomic, retain) NSString *name;修飾的name答應結果為 zhangsanabcname 屬性被修改了; 用 @property (nonatomic, copy) NSString *name;修飾的 name 答應結果為 zhangsanname 屬性沒有被修改。

NSMutableString *str = [NSMutableString string];
str.string = @"zhangsan";
        
Person *zhangsan = [[[Person alloc] init] autorelease];
zhangsan.name = str;
        
[str appendString:@"abc"];
        
NSLog(@"%@ %@",  str, zhangsan.name);
複製程式碼

下面我們來看程式碼set方法的內部實現: 當.h用@property (nonatomic, retain) NSString *name;時,_name = [name retain];相當於[name retain]_name = name;,而這兩句話相當於是先把原來的作引用計數+1,再把指標付給 _name ,實際上指向的是一塊記憶體,這樣會導致原來的內容改變,_name 也會改變,而實際中我們一般不希望 _name 改變,所以我們不用retain。

- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        
        _name = [name retain];
        //_name = [name retain];相當於下邊兩句,而這兩句話相當於是先把原來的作引用計數+1,再把指標付給_name,實際上指向的是一塊記憶體,這樣會導致原來的內容改變,_name也會改變,而實際中我們一般不希望_name改變,所以我們不用retain
//        [name retain];
//        _name = name;
    }
}

- (void)dealloc {
//    [_name release];
//    _name = nil;
    self.name = nil;
    [super dealloc];
}
複製程式碼

當.h用@property (nonatomic, copy) NSString *name;時,當傳入的值為可變物件時,呼叫_name = [name copy]; copy 會建立一個新的物件賦值給 _name,所以 _namename 是兩塊無關的內容,改變 name 不會影響 _name

- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        // 當傳入的值為可變物件時,copy會建立一個新的物件賦值給_name,所以_name和name是兩塊無關的內容,改變name不會影響_name
        _name = [name copy];
    }
}

- (void)dealloc {
//    [_name release];
//    _name = nil;
    self.name = nil;
    [super dealloc];
}
複製程式碼

@synthesize

  1. 在實現檔案中使用 @synthesize propertyName,編譯器先會查詢這個屬性名的setter方法和getter方法有沒有被人為實現,如果已經實現,則不再實現,如果沒有,則會幫我們生成一個屬性命的setter方法和getter方法。
  2. 當在實現檔案中使用了 @synthesize propertyName,編譯器還會做一件事情,在類成員變數中查詢一個名為 _propertyName 的成員變數,如果沒有,再繼續查詢名為 propertyName的成員變數,如果這兩個都沒有,編譯器會自動為我們生成一個私有的名為 _propertyName 的成員變數。注意,系統自動建立的都是私有的。
  3. 當在實現檔案中這樣寫 @synthesize propertyName = varName;時,settergetter 方法所對應的是一個名為 varName 的成員變數,修改和讀取的是 varName 成員變數的值。
  4. 當我們在實現檔案中不寫 @synthesize propertyName 時,在Xcode 4.5之前的版本不會幫我們自動實現 settergetter 方法,系統當然也不再會為我們生成對應的成員變數。但是在Xcode 4.5之後可以不用寫@synthesize了,就跟3、4一樣了。
  5. 當我們既定義了 @synthesize,又在實現檔案中人為重寫 settergetter 方法時,那麼 @synthesize 將不再工作,也就不會為我們建立沒有定義的 _propertyName 成員變數了,這時候如果在 settergetter 方法中呼叫 _propertyName 將會發生編譯錯誤

相關文章