Objective-C的屬性(property)

歸納

img

如下:「attribute(s)」,「特性」是指同一事物(都指@property後面括號內的單詞)。html

用Objective-C作過開發的朋友都知道,類裏面的屬性(能夠近似地理解爲類的變量)是用@property關鍵字定義的,而後@property後面的括號,會寫上若干「特性(attribute)」,後面跟數據類型、屬性名稱。如:程序員

@property (copy, nonatomic) NSString *name;
複製代碼

爲何要有@property?

要搞清楚「特性」,先搞清楚@property,爲何要有@propertyobjective-c

在2006年的WWDC大會上,蘋果發佈了Objective-C 2.0,其中就包括Properties這個新的語法,把原來的實例變量定義成Properties(屬性)。這個變化,和之前相比,有什麼變化呢?緩存

Objective-C2.0以前:

沒有Properties以前,定義實例變量,是這樣的:安全

@interface Person : NSObject {
@public
    NSString *name;
@private
    int age;
}
@end
複製代碼

而後在.h文件,聲明setter和getter方法(setter和getter統稱「accessors/存取器/訪問器」),再在.m文件實現setter和getter,這樣就能夠封裝起來,供其餘類訪問(取值、賦值)了。bash

然而,即便不使用setter和getter,其餘類也能夠經過->來直接訪問,如:app

personA->name = @"123";
NSLog(@"personA->name:%@", personA->name);
複製代碼

爲何要getter和setterless

那麼,爲何還要如此麻煩地聲明和實現setter和getter呢?主要基於三個緣由(參考:Please explain Getter and Setters in Objective C):性能

  • 能夠在getter和setter中添加額外的代碼,實現特定的目的。好比賦值前(set)須要實現一些特定的內部計算,或者更新狀態,緩存數據等等。
  • KVC和KVO都是基於此實現的。
  • 在非ARC時代,能夠在在getter和setter中進行內存管理。

所以,寫getter和setter,可算是Objective-C中「約定俗成」的作法了。(Swift有相似的「Computed Properties/計算屬性」)ui

因此,在沒有Objective-C2.0@property以前,咱們幾乎須要爲全部的實例變量,手動寫getter和setter——聽聽就以爲很可怕,對不對?

Objective-C2.0以後:

慶幸的是,程序員都喜歡「偷懶」,因此就有了2006年Objective-C2.0中的新語法:Properties

它幫咱們自動生成getter和setter(聲明方法,並實現方法。固然,這部分代碼並不會出如今你的項目中,是隱藏起來的)。

不過,@property的寫法,也通過數次變遷(新舊寫法混在一塊兒,就更讓人困惑了):

  • 最開始,須要做3件事情:
    • 在.h文件,咱們用@property聲明瞭屬性——這只是幫咱們在聲明瞭getter和setter;
    • 還須要手動聲明實例變量(和Objective-C2.0以前同樣)
    • 而後在.m文件,還要用@synthesize自動合成getter和setter的實現。
  • 後來,不須要爲屬性聲明實例變量了,@synthesize會默認自動生成一個「下劃線+屬性名」的實例變量。好比@property (copy, nonatomic) NSString *name;以後,就能夠直接使用_name這個變量了。
  • 再後來(Xcode4.5開始),@synthesize也不須要了。一個@property搞定。

因此,如今咱們寫@property聲明屬性,實際上是作了三件事

  • .h: 聲明瞭getter和setter方法;
  • .h: 聲明瞭實例變量(默認:下劃線+屬性名);
  • .m: 實現了getter和setter方法。

這就是@property爲咱們所作的事情。

知道它爲咱們作了什麼,天然也就能回答:「爲何要有@property?」這個問題了。

@property後面的括號又是怎麼回事?

@property (copy, nonatomic) NSString *name;
複製代碼

這種寫法,你們確定都寫過,不過,後面跟着的這個括號又是什麼玩意兒呢?

官方把括號裏面的東西,叫作「attribute/特性」。

先試一下,把括號裏的兩個單詞都刪掉,你會發現,還能正常工做。而事實上,如下兩種寫法,是等價的:

@property () NSString *name;// 或者@property NSString *name;
@property (atomic, strong, readwrite) NSString *name;
複製代碼

由於attribute主要有三種類型(實際上最多能夠寫6個特性,後面詳述),每種類型都有默認值。若是什麼都不寫,系統就會取用默認值(看看,蘋果良苦用心,偷偷幫咱們作了那麼多事情)。

如上所述,attributes有三種類型:

1.Atomicity(原子性)

比較簡單的一句話理解就是:是否給setter和getter加鎖(是否保證setter或者getter的每次訪問是完整性的)。

原子性,有atomic和nonatomic兩個值可選。默認值是atomic(也就是不寫的話,默認是atomic)。

  • atomic(默認值)

使用atomic,在必定程度上能夠保證線程安全,「atomic的做用只是給getter和setter加了個鎖」。也就是說,有線程在訪問setter,其餘線程只能等待完成後才能訪問。

它能保證:即便多個線程「同時」訪問這個變量,atomic會讓你獲得一個有意義的值(valid value)。可是不能保證你得到的是哪一個值(有多是被其餘線程修改過的值,也有多是沒有修改過的值)。

  • nonatomic

而用nonatomic,則不保證你得到的是有效值,若是像上面所述,讀、寫兩個線程同時訪問變量,有可能會給出一個無心義的垃圾值。

這樣對比,atomic就顯得比較雞肋了,由於它並不能徹底保證程序層面的線程安全,又有額外的性能耗費(要對getter和setter進行加鎖操做,我驗證過,在某個小項目中將全部的nonatomic刪除,內存佔用平均升高1M左右)。

因此,你會見到,幾乎全部狀況,咱們都用nonatomic。

2.Access(存取特性)

存取特性有readwrite(默認值)和readonly

這個從名字看就很容易理解,定義了這個屬性是「只讀」,仍是「讀寫」皆可。

若是是readwrite,就是告訴編譯器,同時生成getter和setter。若是是readonly,只生成getter。

3.Storage(內存管理特性)(管理對象的生命週期的)

最經常使用到strongweakassigncopy4個attributes。(還有一個retain,不怎麼用了)

  • strong (默認值)

ARC新增的特性。

代表你須要引用(持有)這個對象(reference to the object),負責保持這個對象的生命週期。

注意,基本數據類型(非對象類型,如int, float, BOOL),默認值並非strong,strong只能用於對象類型。

  • weak

ARC新增的特性。

也會給你一個引用(reference/pointer),指向對象。可是不會主張全部權(claim ownership)。也不會增長retain count。

若是對象A被銷燬,全部指向對象A的弱引用(weak reference)(用weak修飾的屬性),都會自動設置爲nil。

在delegate patterns中經常使用weak解決strong reference cycles(之前叫retain cycles)問題。

  • copy

爲了說明copy,咱們先舉個栗子:

我在某個類(class1)中聲明兩個字符串屬性,一個用copy,一個不用:

@property (copy, nonatomic) NSString *nameCopy;

// 或者能夠省略strong, 編譯器默認取用strong
@property (strong, nonatomic) NSString *nameNonCopy;
複製代碼

在另外一個類中,用一個NSMutableString對這兩個屬性賦值並打印,再修改這個NSMutableString,再打印,看看會發生什麼:

Class1 *testClass1 = [[Class1 alloc] init];

NSMutableString *nameString = [NSMutableString  stringWithFormat:@"Antony"];

// 用賦值NSMutableString給NSString賦值
testClass1.nameCopy = nameString;
testClass1.nameNonCopy = nameString;
   
NSLog(@"修改nameString前, nameCopy: %@; nameNonCopy: %@", testClass1.nameCopy, testClass1.nameNonCopy);

[nameString appendString:@".Wong"];
   
NSLog(@"修改nameString後, nameCopy: %@; nameNonCopy: %@", testClass1.nameCopy, testClass1.nameNonCopy);
複製代碼

打印結果是:

修改nameString前, nameCopy: Antony; nameNonCopy: Antony
修改nameString後, nameCopy: Antony; nameNonCopy: Antony.Wong
複製代碼

我只是修改了nameString,爲何testClass1.nameNonCopy的值沒改,它也跟着變了?

由於strong特性,對對象進行引用計數加1,只是對指向對象的指針進行引用計數加1,這時候,nameStringtestClass1.nameNonCopy指向的實際上是同一個對象(同一塊內存),nameString修改了值,天然影響到testClass1.nameNonCopy

copy這個特性,會在賦值前,複製一個對象,testClass1.nameCopy指向了一個新對象,這時候nameString怎麼修改,也不關它啥事了。應用copy特性,系統應該是在setter中進行了以下操做:

- (void)setNameCopy:(NSString *)nameCopy {
    _nameCopy = [nameCopy copy];
}
複製代碼

你們瞭解copy的做用了吧,是爲了防止屬性被意外修改的。那何時要用到copy呢?

全部有mutable(可變)版本的屬性類型,如NSString, NSArray, NSDictionary等等——他們都有可變的版本類型:NSMutableString, NSMutableArray, NSMutableDictionary。這些類型在屬性賦值時,右邊的值有多是它們的可變版本。

擴展

若是不用copy,而是在賦值前,調用copy方法,能夠達到一樣的目的:

// 這時候也能夠確保nameNonCopy不會被意外修改
testClass1.nameNonCopy = [nameString copy];
複製代碼

若是用copy修飾NSMutableString、NSMutableArray會發生什麼?

若是用copy修飾NSMutableString,在賦值的時候會報以下警告:

Incompatible pointer types assigning to 'NSMutableString *' from 'NSString *'
複製代碼

而若是用copy修飾NSMutableArray,則在調用addObject:時直接crash:

reason: '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x1700045c0'
複製代碼

若是理解了「copy特性,就是在setter中,進行了copy操做」,就很容易知道以上報錯的緣由:屬性在賦值時,調用setter,已經將本來mutable的對象,copy成了immutable的對象(NSMutableString變成NSString,NSMutableArray變成NSArray)。

  • assign

是非ARC時代的特性,

它的做用和weak相似,惟一區別是:若是對象A被銷燬,全部指向這個對象A的assign屬性並不會自動設置爲nil。這時候這些屬性就變成野指針,再訪問這些屬性,程序就會crash。

所以,在ARC下,assign就變成用於修飾基本數據類型(Primitive Type),也就是非對象/非指針數據類型,如:int、BOOL、float等。

注意,在非ARC時代,尚未strong的時候。assign是默認值。ARC下,默認值變成strong了。這個要注意一下,不然會引發困擾。

  • retain

retain是之前非ARC時代的特性,在ARC下並不經常使用。

它是strong的同義詞,二者功能一致。不知道爲何還保留着,這對新手也會形成必定困擾。

因此,總結一下。

  • 幾乎全部狀況,都寫上nonatomic
  • 對外「只讀」的,寫上readonly
  • 通常的對象屬性,寫上strong(用retain也能夠,比較少用)
  • 須要解決strong reference cycles問題的對象屬性,strong改成weak
  • 有mutable(可變)版本的對象屬性,strong改成copy
  • 基本數據類型(int, float, BOOL)(非對象屬性),用assign

4.擴展

其實,除了上面3種常常用到的特性類型,還有2種不太見到。

  • getter=setter=

按字面意思,很容易理解,就是重命名getter和setter方法。

Transitioning to ARC Release Notes中寫道:

You cannot give an accessor a name that begins with new. This in turn means that you can’t, for example, declare a property whose name begins with new unless you specify a different getter

存取方法不能以new開頭,若是你要以new開頭命名一個屬性:@property (copy, nonatomic) NSString *newName;因而會默認生成一個new開頭的getter方法:

這時候就會報錯:Property follows Cocoa naming convention for returning 'owned' objects

解決辦法,就是用**getter=**重命名getter方法:

@property (copy, nonatomic, getter=theNewName) NSString *newName;
複製代碼
  • Nullability
    • nullable:對象「可爲空」
    • nonnull:對象「不可爲空」
    • null_unspecified:「未指定」
    • null_resettable:稍有點難理解,就是調用setter去reset屬性時,能夠傳入nil,可是getter返回值,不爲空。UIView下面的tintColor,就是null_resettable。這樣就保證,即便賦值爲nil,也會返回一個非空的值。

爲了更好地和Swift混編(配合Swift的optional類型),在Xcode 6.3,Objective-C新增了一個語言特性,nullability。具體就是以上4個新特性。

若是設置爲null_resettable,則要重寫setter或getter其中之一,本身作判斷,確保真正返回的值不是nil。不然報警告:Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil

Nullability的寫法以下:

@property (copy, nullable) NSString *name;
@property (copy, readonly, nonnull) NSArray *allItems;

// 也能夠將nullable, nonnull, null_unspecified, null_resettable三個修飾語前面加雙下劃線,用於修飾指針、參數、返回值等(null_resettable只能在屬性括號中使用)
@property (copy, readonly) NSArray * __nonnull allItems;
複製代碼

Nullability的默認值:null_unspecified——未指定。若是某個屬性填寫了Nullability特性(好比寫了nonnull),沒有填寫Nullability的屬性,會出現以下警告:

Pointer is missing a nullability type specifier (_Nonnull, _Nullable, or _Null_unspecified)
複製代碼

可是若是每一個屬性都一一寫上,稍嫌麻煩。而由於大多數屬性是nonnull的,因此蘋果定義了兩個宏,NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END(兩個宏之間,叫作Audited Regions)。

將全部屬性包在這兩個宏中,就無需寫nonnull修飾語了,只須要在「可爲空」的屬性裏,寫上nullable便可:

NS_ASSUME_NONNULL_BEGIN
@interface AAPLList : NSObject <NSCoding, NSCopying>
// 只須要爲「不可爲空」的參數、屬性、返回值加上修飾語nullable便可
- (nullable AAPLListItem *)itemWithName:(NSString *)name;
- (NSInteger)indexOfItem:(AAPLListItem *)item;

@property (copy, nullable) NSString *name;
@property (copy, readonly) NSArray *allItems;
// ...
@end
NS_ASSUME_NONNULL_END
複製代碼

因此!綜上所述,attribute最多能夠寫6個進去:1.原子性、2.存取特性、3.內存管理特性、4.重命名getter、5.重命名setter,6.nullability:

@property (nonatomic, readonly, copy, getter=theNewTitle, setter=setTheNewTitle:, nullable) NSString *newTitle;
複製代碼

不過,應該沒有誰閒得蛋疼會這樣寫的。

最短的寫法就是什麼都不寫,連括號均可以不要:

@property BOOL isOpen;
複製代碼

GG

參考資料

www.jianshu.com/p/035977d1b…

相關文章
相關標籤/搜索