版權聲明:原創做品,謝絕轉載!不然將追究法律責任。html
不少對象須要跟蹤信息爲了執行他們的任務。一些對象設計模型一個或者多個值。例如NSNumber 類用來保存一個值或者自定義的類有一些屬性。有一些對象不在通常的範圍內。也許處理界面交互和一些信息展現這些對象用來跟蹤界面元素或者相關的模型對象。ios
聲明公有的屬性公開數據:web
Objective-c屬性提供了一個定義類信息的方法目的是數據的封裝設計模式
@interface XYZPerson : NSObject安全
@property NSString *firstName;app
@property NSString *lastName;ui
@endatom
在這個例子中這個XYZPerson 類定義了String類型的屬性用來存儲人的名字spa
在面向對象思想中一個只要的原則是對象應該隱藏內部實如今公有接口的後面。咱們訪問對象的屬性而不是直接訪問內部的值。線程
咱們用設置器和訪問器方法來獲取和設置屬性的值
NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@"Johnny"];
默認狀況下這些設置器和訪問器方法是自動合成的由編譯器,所以你不須要作任何事情除了聲明屬性。
合成的方法遵循特定的命名規範:
這個方法用來訪問值(設置器方法)和屬性有相同的名字。
例如:firstName 的訪問器方法名字就叫作firstName
這個方法用來設置值(設置器方法)開始用set而後用上屬性的名字第一個字母大寫:
例如:setFirstName;
若是你不但願屬性改變那麼就用readonly只讀的:
@property (readonly)NSString *fullName;
關鍵字顯示對象怎麼和屬性交互,以及告訴編譯器怎麼合成相關的設置器和訪問器方法。
例如這個編譯器將要合成fullName訪問器方法,可是沒有setFullName方法。
和只讀readOnly相反的是readwrite屬性默認是可讀寫的。
若是你想給訪問器方法設置不一樣的名字,你能夠再給屬性添加他的屬性的時候指定名字。例如Boolean屬性(這個屬性有YES 或者NO值)給他自定義訪問器方法以「IS「開頭
@property (getter = isFinished)BOOL finished;
若是你有多個屬性這樣設置:
@property (readonly,getter = isFinished)BOOL finished;
在這個例子中編譯器合成的是isFinished方法而不是setFinished方法。
點語法是調用的訪問器方法
除了明確的訪問器方法調用Objective-c還提供了點語法訪問對象的屬性。
點語法讓你像這樣訪問屬性:
NSString *firstName = somePerson.firstName;
點語法純粹是一個包裝器方法用來調用訪問器方法的。當用逗號時候,屬性仍然能夠被訪問和修改用getter和setter方法例如:
訪問值用somePerson.firstName 和 [somePerson firstName]:是同樣的。
設置值用somePerson.firstName = @"qiqi";和
[somePerson setFirstName:@「qiqi」];
這就意味着屬性的訪問用點語法也能夠控制屬性。若是屬性是readonly編譯器將要獲得一個警告當你用逗號的時候。
大多數屬性都有實例變量。
默認狀況下,可讀寫的屬性是有輔助實例變量的,再次由編譯器自動合成。
實例變量是一個變量,在整個對象生命週期內都存在。當對象第一次被建立的時候,用於實例變量的內存分配,當對象銷燬的時候就被釋放了。除非有特別的指示不然合成的實例變量和屬性有相同的名字,可是有一個下劃線例如屬性叫作firstName。合成的實例變量的名字叫作_firstName.
雖然他的最佳實踐是一個對象訪問本身的屬性用訪問器或者點語法,他也有可能在實現文件裏直接訪問實例變量用實例方法。下劃線加上實例變量代表你訪問的是實例變量而不是局部變量例如:
- (void)someMethod {
NSString *myString = @"An interesting string";
_someString = myString;
}
這個例子很明顯一個是局部變量和一個實例變量。
一般咱們應該用訪問器方法和點語法來進行屬性的訪問即你訪問對象的屬性用他們本身的實現,在這個例子中咱們能夠用self:
-(void)someMethod
{
NSString *myString = @"qiqi";
self.someString = myString;
//or
[self setSomeString:myString];
}
這個特殊的例外是當寫初始化方法或者銷燬方法或者自定義訪問方法咱們後來描述的。
你能夠自定義合成實例變量的名字
@implementation YourClass
@synthesize propertyName = _instanceVariableName;
@end
例如:
@sythesize firstName = _my_firstName;
在這個例子中,屬性將要被叫作firstName,而且能夠訪問經過firstName而且setFirstName訪問器方法或者點語法,可是有輔助實例變量_my_firstName
重要注意:若是你用@synthesize沒有指定實例變量的名字,就像:
@sytheSize firstName;
這個實例變量將要和你的屬性的名字同樣。
在這個例子中實例變量叫作firstName沒有下劃線。
你能夠定義實例變量而不用屬性
屬性的最佳實踐在對象上是任什麼時候候你須要跟蹤對象的值或者其餘的對象。
若是你須要定義你本身的實例變量沒有定義屬性,你能夠在頭文件或者實現文件裏用以下:
@interface SomeClass:NSObject
{
NSString *_myInstanceVariable;
}
@implementation SomeClass
{
NSString *_anotherCustomInstanceVarible;
}
@end
你能夠添加實例變量在延展裏
訪問實例變量直接從初始化方法裏:
setter方法能夠有額外的反作用。他們能夠出發KVC通知,或者執行進一步的任務若是是你的自定義方法。
你應該直接訪問實例變量從你的初始化方法裏應爲屬性在這時候已經被設置了。其他的對象可能還沒有初始化完成。即便你不提供自定義的訪問器方法在將來的時候可能被其餘的子類給覆蓋。
一個典型的init方法。
- (id)init {
self = [super init];
if (self) {
// initialize instance variables here
}
return self;
}
一個init方法應該分派self和父類初始化方法在作本身的初始化以前。因爲父類可能初始化失敗或者返回nil所以咱們應該檢查來確認self不是nil在咱們本類初始化時候。
在調用父類初始化方法的時候會層層調用父類的初始化方法,當都初始化完畢的時候那麼再初始化本身。
前面咱們看的對象初始化除了調用init方法外,或者調用方法初始化特殊值。
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName;
你能夠實現這個方法像這樣:
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName {
self = [super init];
if (self) {
_firstName = aFirstName;
_lastName = aLastName;
}
return self;
}
指定初始化是主要的初始化方法:
若是一個對象一個或者多個初始化方法,你應該定義哪一個方法是設計的初始化者,這個提供了不少可選的初始化(例如能夠有不少的參數),而且能夠爲其餘的方法調用提供便利,你應該也典型重載初始化方法來給你的初始化方法設定默認的值。
若是下面這個方法的類有生日的屬性,咱們設計初始化方法可能這樣:
- (id)initWithFirstName:(NSString *)aFirstName lastName:(NSString *)aLastName dateOfBirth:(NSDate *)aDOB;
這個方法將要設置咱們的實例變量,若是你將提供便利初始化第一個和第二個名字,你將要這樣實現你的初始化方法:
- (id)initWithFirstName:(NSString *) aFrameworkName lastName:(NSString *)aLastName
{
return [self initWithFirstName:aFirstName lastName:aLastName dateOfBirth:nil];
}
你能夠實現標準的init方法來提供適當的默認值
- (id)init
{
return [self initWithFirstName:@"qiqi" lastName@"deo" dateOfBirth:nil];
}
你須要寫初始化方法當子類用多個初始化方法,你應該重寫父類的初始化方法,來實現本身的初始化,或者添加本身的初始化方法,可是都得初始化父類在初始化本身以前。
你能夠實現本身的訪問器方法:
屬性不能一直有本身的輔助實例變量。
舉一個例子咱們爲了跟蹤屬性的值能夠自定義打印出他的值例如:
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
若是你須要寫自定義的訪問器方法爲屬性而且有一個實例變量。你必須直接定義實例變量。例如:咱們常常在屬性裏面延遲初始化實例變量懶加載設計模式:
- (XYZObject *)someImportantObject {
if (!_someImportantObject) {
_someImportantObject = [[XYZObject alloc] init];
}
return _someImportantObject;
}
再返回值以前須要檢測返回值是否是nil,若是爲空那麼就初始化對象。
編譯器會自動合成一個實例變量在全部的狀況下,它也是合成只要一個訪問器方法。若是你本身實現訪問器和設置器方法,那麼編譯器會認爲你本身控制屬性的實現,而不會自動的生成實例變量。
若是你仍然須要實例變量的話,你將要須要請求一個合成。
@synthesize property = _property;
屬性默認的是原子性的:
默認的Objective-c屬性是原子性的:
@interface XYZObject : NSObject
@property NSObject *implicitAtomicObject; // atomic by default
@property (atomic) NSObject *explicitAtomicObject; // explicitly marked atomic
@end
注意:屬性的原子性的特性並非意味着這個對象的線程是安全的。咱們想象一下,一個對象有名字和姓在原子性的訪問器被訪問。若是一個線程同時訪問他們的名和姓,這個原子性的訪問方法不必定正確返回值。若是名字在訪問以前改變,姓在訪問以後改變,你會有一個不一致,不對應的結果,那麼這不是你想要的結果。
經過對象的全部權和責任管理對象圖
就像你已經看到的,Objective-c的對象是動態建立的在堆上,這意味着你須要用指針來跟蹤對象地址,不像常量,不是一直能肯定對象的生命週期的範圍經過指針變量,相反的是一個對象必須長時間保存在內存中以便於其餘的對象引用。
不要試圖擔憂手動管理每一個對象的生命週期,你應該試圖考慮對象之間的關係。
例如這個XYZPerson對象,例如,有兩個名字的屬性有效的被XYZPerson實例擁有,這意味着他們應該呆在內存足夠長只要XYZPerson在內存中。
當一個對象依賴其餘的對象用這種方式,有效的採起其餘對象的全部權,這個第一個對象強引用其餘的對象。在Objective-c一個對象保持存活狀態只要他最少有一個強引用指向他從別的對象:
當XYZPerson對象被銷燬從內存,這兩個對象也將要被銷燬,假設沒有其餘的強引用指向他們。
下面有一個更復雜的例子:
當我點擊更新的按鈕,這個 badge preview更新相關名字信息。
第一次人的詳細信息被輸入和更新以下圖:
當用戶修改這我的的名字變成以下:
這個View管理一個強引用關係到字符串對象儘管XYZPerson有不一樣的名字,這意味這這個字符串對象將在內存中被View來打印和使用。
當第二次點擊的時候這個View被告訴來更新內部的屬性匹配人的對象。所以變成以下圖:
到如今,這個字符串對象沒有強引用指向他,他就會被從內存中移除掉。
默認的Objective-c的屬性和實例變量維持着強引用指向其餘對象, 這只是在合適的狀況下,可是會引發潛在的問題強引用循環。
怎麼避免強引用
儘管強應用適合單向對象之間的關係,你須要當心處理組相互關聯的對象,若是一個組的對象組有一圈強引用,他們相互存活即便他們沒有強引用從組的外部。
一個明顯的例子在tableView和他的delegate以前有潛在的強引用存在。爲了使通用的tableView這個類在不少環境能用到。他的delegate作出一些決定給外部對象。這意味着他表現什麼依賴與另外一個對象,或者作什麼若是用戶和表示圖交互。下圖:
這樣將會出現一個問題若是其餘的對象放棄對tableview和delegate以下圖:
即便不須要對象們存活在內存中,沒有強引用指向tableView或者delegate不一樣於兩個對象之間的關係,剩下的兩個強引用保持着他們之間的關係。可是這是個強引用的循環。
有一個方法來解決這個問題就是替代其中一個強引用爲弱引用。弱引用並不意味着兩個對象以前的全部權,而且不能保持一個對象的存活。
如今變成這樣下圖:
當沒有其餘對象給tableView或者tableView的delegate,就沒有強引用指向delegate對象以下圖:
這意味着delegate對象將要被銷燬,從而釋放了tableView的強引用。
一旦delegate被銷燬,就沒有其餘的強引用指向tableView,所以他也被銷燬。
使用強和弱來管理全部權
默認狀況下,對象屬性這樣聲明
@property id delegate;
用強引用來實現實例變量,聲明一個弱引用,給他添加屬性想這樣:
@property (weak)id delegate;
注意:weak的相反是strong。因此沒有必要聲明,默認是strong
局部變量(不是屬性的實例變量)是默認保持強引用的,這意味着下面代碼將要像你預想的這樣工做:
NSDate *originalDate = self.lastModificationDate;
self.lastModificationDate = [NSDate date];
NSLog(@"Last modification date changed from %@ to %@",
originalDate, self.lastModificationDate);
在這個例子中,局部變量originalDate保持着強引用對lastModification對象。當lastmodification屬性改變的時候,這個屬性不能保持強引用給原始的date,可是date是繼續保持存活因爲originalDate強引用變量。
注意:一個變量維持着強引用給另外一個對象只要這個變量在這個範圍內,或者直到他分配給另外一個對象或者nil。
若是你不想要一個變量維持強引用,你能夠聲明弱引用像這樣:
NSObject * __weak weakVariable;
由於弱引用不能保持對象存活,有可能引用對象被銷燬而後這個引用仍然被使用。爲了不這個懸空的指針指向以前原始對象佔用的內存。弱引用須要自動把他設置爲空當對象釋放的時候。
這意味着若是你用弱引用在以前的例子中:
這個originalDate的變量可能潛在的被設置爲空。當self.lastModification被再次指定,這個屬性不能維持強引用關係對originaldate。若是沒有強引用指向他,這個原始日期將要被銷燬而且originaldate被設置nil。
弱的引用多是困惑的來源像這樣:
NSObject * __weak someObject = [[NSObject alloc] init];
這個例子,剛建立的對象沒有強引用對他,所以很快被釋放而且指針指向nil。
NSObject *cachedObject = self.someWeakProperty; // 1
if (cachedObject) { // 2
[someObject doSomethingImportantWith:cachedObject]; // 3
} // 4
cachedObject = nil; // 5
在這個例子中,強引用在第一行建立,意味着對象確保在下面兩個方法調用的時候仍然存活着。在第5行這個對象被設置爲nil。今後放棄了強引用,若是原始對象沒有其餘的強引用指向他,他就會被銷燬而且指針指向空。
用Unsafe Unretained引用對一些類
有一些類在cocoa和cocoa Touch不支持弱引用。這意味着你不能定義弱引用給屬性和局部變量來追蹤他們,這些類包括NSTextView,NSFont,和NSColorSpace,還有不少在Transitioning to ARC Release Notes.筆記裏。
若是你須要弱引用對一些類你必須用不安全的引用。對於一個屬性,這意味着用Unsafe Unretained
@property (unsafe_unretained) NSObject *unsafeProperty;
對於變量,你須要這樣使用_unsafe_untetained
NSObject *_unsafe_unretained unsafeReference;
這個引用有點像弱引用,他不讓他的相關對象活着,可是不能設置nil若是目標對象被銷燬了。這意味着你將要遺留一個懸空指針指向原來對象佔用的那塊內存,所以屬於不安全是由於發送一個消息到懸空指針結果是崩潰的。
複製屬性維持着他們的拷貝
在一些狀況下一個對象也許但願保持他的一份copy。
@interface XYZBadgeView : NSView
@property NSString *firstName;
@property NSString *lastName;
@end
例如這個類的接口聲明兩個屬性默認是強引用的。想一想會發生什麼若是另外一個對象建立一個字符串設置他的屬性像這樣:
NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
self.badgeView.firstName = nameString;
這個完美有效的,由於NSMutableString是NSString的子類。儘管這個View認爲這是處理NSString的一個實例,這意味着這個字符串對象是能夠被改變的:[nameString appending:@"qiqi"];
在這個例子當中,儘管這個名字是@「john」在當時建立的時候被設置爲View的firstName屬性,可是如今由於改變告終果變成「johnqiqi」由於可變字符串是被改變了。
你可能選擇這個View應該維持他本身的副本包括他的屬性,因此他有效的被撲捉這個字符串在這個屬性被設置的時候。添加兩個屬性他們的屬性關鍵字是copy。
@interface XYZBadgeView : NSView
@property (copy) NSString *firstName;
@property (copy) NSString *lastName;
@end
這個View維持着他們本身的copy裏面有兩個字符串,即便一個可變的字符串被設置隨後又發生了改變,這個View捕捉了在那時候設置的值,例如:
NSMutableString *nameString = [NSMutableString stringWithString:@"John"];
self.badgeView.firstName = nameString;
[nameString appendString:@"ny"];
這時候firstName被View擁有將是一個未受影響的copy對原始的「John」字符串。這個copy關鍵字屬性意味着屬性將要用的是強引用,由於他可能被新建立的對象所擁有。
注意:任何屬性帶有copy關鍵字的都必須支持NSCopying協議,這意味着他應該符合NSCopying協議。
若是你須要直接設置copy屬性的輔助實例變量,例如一個初始化方法,不要忘了設置copy原始的對象。例如:
- (id)initWithSomeOriginalString:(NSString *)aString {
self = [super init];
if (self) {
_instanceVariableForCopyProperty = [aString copy];
}
return self;
}