iOS @property 屬性相關的總結
讀寫屬性: readwrite
、 readonly
setter語意: assign
、 retain
/ copy
原子性(多執行緒管理): atomic
、 nonatomic
強弱引用: strong
、 weak
-
讀寫屬性:
readwrite
:同時生成set
和get
方法(預設)readonly
:只會生成get
方法 -
控制set方法的記憶體管理:
retain
:release
舊值,retain
新值。希望獲得源物件的所有權時,對其他NSObject
和其子類(用於OC
物件)copy
:release
舊值,copy
新值。希望獲得源物件的副本而不改變源物件內容時(一般用於NSString
,block
)assign
:直接賦值,不做任何記憶體管理(預設屬性),控制需不需生成set
方法。對基礎資料型別 (NSInteger
,CGFloat
)和C資料型別(int
,float
,double
,char
, 等等),另外還有id型別 -
原子性(多執行緒管理):
-
atomic
預設屬性,訪問方法都為原子型事務訪問。鎖被加到所屬物件例項級,效能低。原子性就是說一個操作不可以中途被 cpu 暫停然後排程, 即不能被中斷, 要不就執行完, 要不就不執行. 如果一個操作是原子性的,那麼在多執行緒環境下, 就不會出現變數被修改等奇怪的問題。原子操作就是不可再分的操作,在多執行緒程式中原子操作是一個非常重要的概念,它常常用來實現一些同步機制,同時也是一些常見的多執行緒 Bug 的源頭。當然,原子性的變數在執行效率上要低些。 -
nonatomic
非原子性訪問。不加同步,儘量避免多執行緒搶奪同一塊資源。是直接從記憶體中取數值,因為它是從記憶體中取得資料,它並沒有一個加鎖的保護來用於cpu中的暫存器計算Value,它只是單純的從記憶體地址中,當前的記憶體儲存的資料結果來進行使用。 多執行緒併發訪問會提高效能,但無法保證資料同步。儘量避免多執行緒搶奪同一塊資源,否則儘量將加鎖資源搶奪的業務邏輯交給伺服器處理,減少移動客戶端的壓力。
當有多個執行緒需要訪問到同一個資料時,OC中,我們可以使用@synchronized
(變數)來對該變數進行加鎖(加鎖的目的常常是為了同步或保證原子操作)。
-
-
強指標(strong)、弱指標(weak)
-
strong
strong
系統一般不會自動釋放,在oc
中,物件預設為強指標。作用域銷燬時銷燬引用。在實際開放中一般屬性物件一般strong
來修飾(NSArray
,NSDictionary
),在使用懶載入定義控制元件的時候,一般也用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
是所指物件的地址,Value
是weak
指標的地址(這個地址的值是所指物件的地址)陣列。(備註:strong
是通過runtime
維護的一個自動計數表結構)weak 的實現原理可概括三步:
- 初始化時:
runtime
會呼叫objc_initWeak
函式,初始化一個新的weak
指標指向物件的地址。 - 新增引用時:
objc_initWeak
函式會呼叫objc_storeWeak()
函式,objc_storeWeak()
的作用是更新指標指向,建立對應的弱引用表。 - 釋放時,呼叫
clearDeallocating
函式。clearDeallocating
函式首先根據物件地址獲取所有weak
指標地址的陣列,然後遍歷這個陣列把其中的資料設為nil
,最後把這個entry
從weak
表中刪除,最後清理物件的記錄。
- 初始化時:
深淺複製
深複製與淺複製
- 深複製
- 源物件和副本物件不同的各兩個物件
- 源物件的引用計數器不變,副本物件的引用計數器為 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
),只是Foundation庫中的類的物件可以直接複製,自定義的類的物件需要做一些額外的工作才能複製,但實際做app幾乎不需要複製自定義類的物件。
不可變物件和可變物件的 copy
、 mutableCopy
對比
不可變物件 copy
成 不可變物件 是淺複製,其他都是深複製。(不可變物件指NSArray、NSDictionary、NSString等)

copy 與 strong
NSMutableArray
被 copy
、 strong
修飾後的變化 把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
;右邊是什麼,左邊就是什麼,並且是強引用新值,左邊的型別會與右邊的相同,不會改變。
文章推薦
NSString 為什麼用 copy 而不用 retain
我們通過實際操作來說明,我們把str賦值給 zhangsan
的 name
屬性,然後去改變 str
,結果: 用 @property (nonatomic, retain) NSString *name;
修飾的name答應結果為 zhangsanabc
, name
屬性被修改了; 用 @property (nonatomic, copy) NSString *name;
修飾的 name
答應結果為 zhangsan
, name
屬性沒有被修改。
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
,所以 _name
和 name
是兩塊無關的內容,改變 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]; } 複製程式碼