@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
表中刪除,最後清理物件的記錄。
- 初始化時:
-
文章推薦:
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
),只是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
;右邊是什麼,左邊就是什麼,並且是強引用新值,左邊的型別會與右邊的相同,不會改變。
文章推薦
iOS 深拷貝淺拷貝與@property 引用計數關鍵字Strong,Copy,Weak,Assign
iOS 淺談:深.淺拷貝與copy.strong
陣列的屬性修飾符到底用strong還是copy?
iOS中copy和mutableCopy的詳細分析
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];
}
複製程式碼
@synthesize
- 在實現檔案中使用
@synthesize propertyName
,編譯器先會查詢這個屬性名的setter方法和getter方法有沒有被人為實現,如果已經實現,則不再實現,如果沒有,則會幫我們生成一個屬性命的setter方法和getter方法。 - 當在實現檔案中使用了
@synthesize propertyName
,編譯器還會做一件事情,在類成員變數中查詢一個名為_propertyName
的成員變數,如果沒有,再繼續查詢名為propertyName
的成員變數,如果這兩個都沒有,編譯器會自動為我們生成一個私有的名為_propertyName
的成員變數。注意,系統自動建立的都是私有的。 - 當在實現檔案中這樣寫
@synthesize propertyName = varName
;時,setter
和getter
方法所對應的是一個名為varName
的成員變數,修改和讀取的是varName
成員變數的值。 - 當我們在實現檔案中不寫
@synthesize propertyName
時,在Xcode 4.5之前的版本不會幫我們自動實現setter
和getter
方法,系統當然也不再會為我們生成對應的成員變數。但是在Xcode 4.5之後可以不用寫@synthesize了,就跟3、4一樣了。 - 當我們既定義了
@synthesize
,又在實現檔案中人為重寫setter
和getter
方法時,那麼@synthesize
將不再工作,也就不會為我們建立沒有定義的_propertyName
成員變數了,這時候如果在setter
和getter
方法中呼叫_propertyName
將會發生編譯錯誤