OC 30 day
在尋找相關資料時,看到“簡書”有一篇非常好的文章,在這邊分享。
資料來源:https://www.jianshu.com/p/035977d1ba89
先看一張圖:
在2006年的WWDC大會上,蘋果發布了Objective-C 2.0,其中就包括Properties這個新的語法,把原來的實例變量定義成Properties(屬性)。
那麼,為什麼還要如此麻煩地聲明和實現setter和getter呢?主要基於三個原因(參考: Please explain Getter and Setters in Objective C ):
因此,寫getter和setter,可算是Objective-C中「約定俗成」的做法了。(Swift有類似的「Computed Properties/計算屬性」)
所以,在沒有Objective-C2.0的@property之前,我們幾乎需要為所有的實例變量,手動寫getter和setter——聽听就覺得很可怕,對不對?
慶幸的是,程序員都喜歡「偷懶」,所以就有了2006年Objective-C2.0中的新語法:Properties。
它幫我們自動生成getter和setter (聲明方法,並實現方法。當然,這部分代碼並不會出現在你的項目中,是隱藏起來的)。
不過,@property的寫法,也經過數次變遷(新舊寫法混在一起,就更讓人困惑了):
.h: 聲明了getter和setter方法;
.h: 聲明了實例變量(默認:下劃線+屬性名);
.m: 實現了getter和setter方法。
這就是@property為我們所做的事情。
知道它為我們做了什麼,自然也就能回答:「為什麼要有@property?」這個問題了。
@property (copy, nonatomic) NSString *name;
這種寫法,大家肯定都寫過,不過,後面跟著的這個括號又是什麼玩意兒呢?
官方把括號裡面的東西,叫做「attribute/特性」。
先試一下,把括號裡的兩個單詞都刪掉,你會發現,還能正常工作。而事實上,以下兩種寫法,是等價的:
@property () NSString *name;// 或者@property NSString *name;
@property (atomic, strong, readwrite) NSString *name;
因為attribute主要有三種類型(實際上最多可以寫6個特性,後面詳述),每種類型都有默認值。如果什麼都不寫,系統就會取用默認值(看看,蘋果良苦用心,偷偷幫我們做了那麼多事情)。
如上所述,attributes有三種類型:
比較簡單的一句話理解就是:是否給setter和getter加鎖(是否保證setter或者getter的每次訪問是完整性的)。
原子性,有atomic和nonatomic兩個值可選。默認值是atomic(也就是不寫的話,默認是atomic)。
它能保證:即使多個線程「同時」訪問這個變量,atomic會讓你得到一個有意義的值(valid value)。但是不能保證你獲得的是哪個值(有可能是被其他線程修改過的值,也有可能是沒有修改過的值)。
這樣對比,atomic就顯得比較雞肋了,因為它並不能完全保證程序層面的線程安全,又有額外的性能耗費(要對getter和setter進行加鎖操作,我驗證過,在某個小項目中將所有的nonatomic刪除,內存佔用平均升高1M左右)。
所以,你會見到,幾乎所有情況,我們都用nonatomic。
存取特性有readwrite (默認值)和readonly。
這個從名字看就很容易理解,定義了這個屬性是「只讀」,還是「讀寫」皆可。
如果是readwrite,就是告訴編譯器,同時生成getter和setter。如果是readonly,只生成getter。
最常用到strong、weak、assign、copy 4個attributes。(還有一個retain,不怎麼用了)
表明你需要引用(持有)這個對象(reference to the object),負責保持這個對象的生命週期。
注意,基本數據類型(非對像類型,如int, float, BOOL),默認值並不是strong,strong只能用於對像類型。
也會給你一個引用(reference/pointer),指向對象。但是不會主張所有權(claim ownership)。也不會增加retain count。
如果對象A被銷毀,所有指向對象A的弱引用(weak reference)(用weak修飾的屬性),都會自動設置為nil。
在delegate patterns中常用weak解決strong reference cycles(以前叫retain cycles)問題。
我在某個類(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,這時候,nameString和testClass1.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,而是在賦值前,調用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)。
是非ARC時代的特性,
它的作用和weak類似,唯一區別是:如果對象A被銷毀,所有指向這個對象A的assign屬性並不會自動設置為nil。這時候這些屬性就變成野指針,再訪問這些屬性,程序就會crash。
因此,在ARC下,assign就變成用於修飾基本數據類型(Primitive Type),也就是非對象/非指針數據類型,如:int、BOOL、float等。
注意,在非ARC時代,還沒有strong的時候。assign是默認值。ARC下,默認值變成strong了。這個要注意一下,否則會引起困擾。
retain是以前非ARC時代的特性,在ARC下並不常用。
它是strong的同義詞,兩者功能一致。不知道為什麼還保留著,這對新手也會造成一定困擾。
所以,總結一下。
其實,除了上面3種經常用到的特性類型,還有2種不太見到。
按字面意思,很容易理解,就是重命名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;
如果設置為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_BEGIN和NS_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;
資料來源:https://www.jianshu.com/p/035977d1ba89