一. 成員變量,實例變量,屬性變量
成員變量 : 用在類的內部,無須與外部接觸.成員變量默認是被保護的,所以不會有setter和getter方法. 成員變量是定義在{}中的變量.
實例變量: 如果變量類型是一個類. 如: UILabel * label;那么這個變量就是實例化變量. 所以實例化變量也是成員變量的一種. 不需要與外部接觸.或者稱為類的私有變量.
屬性量.
@property (nonatomic, copy) NSString *age;
屬性變量聲明之后,編譯器會自動生成一個以下劃線開頭的實例變量_age. 不需要自己手動再去寫實例變量. 也會自動生成對應的setter和getter.
二. 屬性變量的getter和setter方法
- setter: 給外部提供一個修改內部屬性的接口,通過給對象指針發送該消息(調用setter方法)可以做到修改內部的屬性值.
- getter: 為外部提供的一個查看內部變量的接口.
- 舉例說明.
UILabel * label = [[UILabel alloc] init]; [label setText:@"這是一個label"]; // 外部調用UILabel內>部的text屬性的setter方法,修改屬性值. NSString * textStr = [label text];// 外部訪問UILabel的getter方法,讀取該屬性的值. NSLog(@"textStr = %@",textStr); //setter方法 - (void)setAge:(NSString *)age { _age = age; } //getter方法 - (NSString *)age { return _age; } // 點調用 label.text = @"這是一個label"; // '.'調用在'='左邊相當于setter textStr = label.text; // '.'調用在'='右邊相當于getter
- 實戰
(1). setter: 可以添加一個規則來保證set的值是否正確等用法.// 重寫set方法,并保證該屬性的值為 >= 1 - (void)setCount:(int)count { if (count < 1) { count = 1; } _count = count; }
(2). getter : 可以精簡代碼等其他好處.
聲明一個UIColor的對象屬性.每當該類中的一個label的背景顏色改變之后,就 賦值給這個對象.那么每次都要讀取這個label的顏色屬性. 但是如果用getter方法就可以簡化為- (UIColor *)color { return label.backgroundColor; }
二. 原子性修飾符 atomic / nonatomic
- atomic : 原子性. 為setter方法加鎖.線程安全,但需要消耗大量的資源. 屬性默認為原子性atomic.
- nonatomic : 非原子性. 不為setter方法加鎖.線程不安全.適合.資源占用低.
- 在多線程中原子操作是必須的.之所以這么做就是為了保證在寫未完成的時候被另一個線程讀取.造成數據錯誤.經典案例: 火車票的預定和購買. 加入atomic屬性修飾之后,setter方法就會加鎖.
{lock} if (property != newValue) { [property release]; property = [newValue retain]; } {unlock}
- nonatomic直接訪問內存的地址,不關心其他線程是否改變整個值,并且沒有死鎖現保護.只需要從內存中訪問到當前內存地址中能用到的數據即可.
- 不要誤以為多線程加了atomic就是安全的. atomic只有在setter和getter的時候是原子操作.其他方面就不是atomic能管理的了. 想要安全就需要其他線程安全的操作了,比如加鎖.
三. 讀寫型修飾符
- readonly: 表明這個屬性只能讀,不能寫.系統只為我們生成一個getter方法下劃線開頭的成員變量.不會創建setter方法.
當希望外界能讀取我們這個屬性,但是不希望被外界改變的時候就用readonly。- readwrite: 表明這個屬性是可讀可寫的. 系統為我們這個屬性生成了setter和getter方法.
- 系統默認為readwrite.
- 一般我們封裝的方法只允許外界read不允許寫. 在.h文件里用readonly修飾,在.m文件里面用readwrite修飾。這樣就可以外部只讀,內部讀寫.
// .h文件 #import <UIKit/UIKit.h> @interface SecondViewController : UIViewController @property (nonatomic, strong, readonly) NSString * str; @end
// .m文件 #import "SecondViewController.h" @interface SecondViewController () @property (nonatomic, strong, readwrite) NSString * str; @end
四. 預備知識
內存的棧區 : 由編譯器自動分配釋放, 存放函數的參數值, 局部變量的值等.
內存的堆區 : 一般由程序員分配釋放, 若程序員不釋放, 程序結束時可能由OS回收.
五. copy
- copy 和 mutableCopy
如果想要創建一個對象,該對象與源的內容一致,那么可以用拷貝(copy或mutableCopy).
copy拷貝出來的對象類型總是不可變類型(例如, NSString, NSDictionary, NSArray等等)
mutableCopy拷貝出來的對象類型總是可變類型(例如, NSMutableString, NSMutableDictionary, NSMutableArray等等)NSString *string = @"Jerry"; [string copy] --> 拷貝出內容為Jerry的NSString類型的字符串 [string mutableCopy] --> 拷貝出內容為Jerry的>NSMutableString類型的字符串 NSDictionary *dict = @{@"name" : @"Jerry"}; [dict copy] --> 拷貝出內容與dict相同的NSDictionary類型的字典 [dict mutableCopy] --> 拷貝出內容與dict相同的>NSMutableDictionary類型的字典 NSArray *array = @[@"Jerry"]; [array copy] --> 拷貝出內容與array相同的NSArray類型的數組 [array mutableCopy] --> 拷貝出內容與array相同的>NSMutableArray類型的數組
copy和mutableCopy
- block為什么用copy.
block是一個對象,所以block在創建的時候內存是默認在stack(棧)上的. 而不是在heap(堆)上的.所以他的作用域僅限創建時候的當前上下文(函數,方法等),當在作用域外調用block就會崩潰. Copy可以將block從內存棧區移動到堆區.這樣在作用域外也不會崩潰了. 但在ARC下, 使用copy與strong其實都一樣, 因為block的retain就是用copy來實現的.block還是建議使用copy修飾.因為MRC下就是就是用copy修飾的.- copy相對于直接賦值的好處.
先來看這個兩個的區別.NSArray * array; NSMutableArray * arrayM = [NSMutableArray array]; [arrayM addObject:@"A"]; array = arrayM; [arrayM addObject:@"B"]; NSLog(@"array = %@, arrayM = %@",array,arrayM); // 結果 array = ( "A", "B" ), arrayM = ( "A", "B" )
明明可變數組添加對象是在賦值之后, 為什么后面添加對象還會影響到不可變數組呢?
因為Objective-C支持多態.所以表面上array是NSArray對象,但是其骨子里還是NSMutableArray對象.
這樣的話將會對后期DEBUG增加很大的成本, 可能會導致莫名其妙的錯誤.NSArray * array; NSMutableArray * arrayM = [NSMutableArray array]; [arrayM addObject:@"A"]; array = [arrayM copy]; // 此處有不同 [arrayM addObject:@"B"]; NSLog(@"array = %@, arrayM = %@",array,arrayM); // 結果 array = ( "A" ), arrayM = ( "A", "B" )
這樣就能保證不管賦值的是可變還是不可變數組, NSArray就是NSArray了!
所以@property中NSString,NSArray,NSDictionary屬性用copy而不是strong了.
如果能夠在你的工程中正確使用copy, 將會對你的程序有不小的幫助.細節決定成敗.
- 深拷貝和淺拷貝
深拷貝(內容拷貝): 直接拷貝整個對象內容到另一塊內存中.
淺拷貝(指針拷貝): 并不拷貝對象本身,僅僅是拷貝指向對象的指針,指向該內存地址.拷貝出來的對象與源對象的地址一致!這意味著修改拷貝對象的值會直接影響到源對象.如果在多層數組中,對第一層進行內容拷貝,其它層進行指針拷貝,這種情況是屬于深復制,還是淺復制?對此,蘋果官網文檔有這樣一句話描述:
This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy... If you need a true deep copy, such as when you have an array of arrays...
從文中可以看出,蘋果認為這種復制不是真正的深復制,而是將其稱為單層深復制(one-level-deep copy)。因此,有人對淺復制、完全深復制、單層深復制做了概念區分。當然,這些都是概念性的東西,沒有必要糾結于此。只要知道進行拷貝操作時,被拷貝的是指針還是內容即可。
5. 自定義復制
先自定義一個MyPerson的類.初始化并進行copy或者mutableCopy會出現如圖問題.找不到copyWithZone或者mutableCopyWithZone方法.
其實當程序調用對象的copy方法來復制自身時,底層需要調用copyWithZone:方法來完成實際的復制工作,copy返回實際上就是copyWithZone:方法的返回值;mutableCopy與mutableCopyWithZone:方法也是同樣的道理。自定義類的copy.png
那么怎么做才能讓自定義的對象進行copy與mutableCopy呢?需要做以下事情:
1.讓類實現NSCopying/NSMutableCopying協議。遵守NSCoding協議.png
2.讓類實現copyWithZone:/mutableCopyWithZone:方法實現copyWithZero.png
該段參考:
小結iOS中的copy
iOS之對象復制
六. assign
- assign是賦值屬性.引用計數不加1.
- 一般用來修飾基礎數據類型(NSInteger,CGFloat等)和C數據類型(int,float,double)等.
- assign是指針賦值,不對引用計數操作,使用之后如果沒有置為nil,可能就會產生野指針.指向對象地址但計數不+1,但當地址引用計數為0時,assign不會對地址進行數據的抹除操作,只是進行值釋放。這就導致野指針存在,即當這塊地址還沒寫上其他值前,能輸出正常值,但一旦重新寫上數據,該指針隨時可能沒有值,造成奔潰。
七. weak
- 引用計數不加1.
- 當使用weak修飾的屬性,當對象釋放的時候,系統會對屬性賦值nil,objective-c有個特性就是對nil對象發送消息也就是調用方法。weak特性要求不保留傳入的對象。如果該對象被釋放,那么相應的實例變量會被自動賦為nil。這么做可以避免產生懸空指針。懸空指針指向的是不再存在的對象。向懸空指針發送消息通常會導致程序崩潰。相應的存方法會將傳入的對象直接賦給實例變量。
- weak只能修飾對象類型.
weak只能修飾對象類型- 用weak修飾代理屬性和用來解決循環強引用.
八.retain
- retain用在MRC情況下,被retain修飾的對象,引用計數retainCount要加1的。
- retain只能修飾oc對象,不能修飾非oc對象,比如說CoreFoundation對象就是C語言框架,它沒有引用計數,也不能用retain進行修飾。
- retain一般用來修飾非NSString 的NSObject類和其子類。
九. strong
- 表示對對象的強引用.
- strong和weak默認用strong
- retainCount + 1
- 對兩個對象之間互相強引用造成循環引用,內存泄露.