很多人講屬性修飾詞的時候,喜歡從字面或者定義的角度介紹它們間的區別。這篇文章,我們側重從修飾詞對setter方法的影響直接展示區別。
1. 實例變量:命名區別于全局變量和局部變量
1.1 命名法則:
- 以下劃線
_
作為實例變量名字的前綴,如_student
- 這樣,可以很容易地通過下劃線區分實例變量與其它變量(全局變量,局部變量,靜態變量等)
1.2 聲明位置:
- 在.h頭文件中
- 或者,在.m實現文件的類拓展 中
1.3 聲明形式:
- 頭文件,寫在類似
@interface Person : NSObject {...}
這樣的花括號{...}
里面 - 實現文件的類拓展中,寫在類似
@interface Person ()<UIScrollViewDelegate> {...}
這樣的花括號{...}
里面
1.4 聲明示例:
@interface TYPagerController ()<UIScrollViewDelegate> {
NSInteger _countOfControllers;
BOOL _needLayoutContentView;
CGFloat _preOffsetX;
float _heightInMeters;
struct {
unsigned int transitionFromIndexToIndex :1;
unsigned int transitionFromIndexToIndexProgress :1;
}_delegateFlags;
struct {
unsigned int transitionFromIndexToIndex :1;
unsigned int transitionFromIndexToIndexProgress :1;
}_methodFlags;
}
1.5 setter、getter方法
可以自己手動為實例變量在頭文件 中聲明setter、getter方法,并在實現文件中實現setter、getter方法。你也可以不聲明不實現,但不要再企圖調用setter、getter方法了,甚至點語法。
//一個例子
//實現getter
- (float)heightInMeters{
return _heightInMeters;
}
//實現setter
- (float)setHeightInMeters:(float)h{
_heightInMeters = h;
}
1.6 調用setter、getter方法
如果你實現了setter,getter方法,才可以調用存取方法,例如:
//調用getter
float temp = [self heightInMeters];
//調用setter
[self setHeightInMeters:10.0];
1.7 點語法
如果你實現了setter,getter方法,才可以使用點語法 簡化調用存取方法。
//調用getter
float temp = self.heightInMeters;
//調用setter
self.heightInMeters = 10.0;
1.8 實例變量類型:
- 1)可以是簡單的C類型,如
int _sudentNum;
,float _heightInMeters;
這種實例變量及其值會在聲明對象的內部保存。
- 2)可以是指針,用來指向其他對象,如
NSString *tempStr
,CMPersonModel *personModel
等等。這種屬性叫對象實例變量。
這種變量,聲明的對象內部僅保存指向相應實例對象的指針(對象地址),而不保存實例對象本身。實例對象本身由堆負責保存,管理機制由ARC負責。
1.9 繼承特性:
- 子類繼承不了父類寫在類拓展 中的示例變量
2. 屬性:自動聲明實例變量和存取方法,并實現存取方法
2.1 聲明位置:
- 聲明頭文件
- 或者實現文件的類拓展中
2.2 聲明形式:
- 寫在@interface與@end之間,花括號
{...}
之外 - 必須有@property修飾詞修飾,后面可選擇性地添加其他修飾詞如(nonatomic, strong) 等
2.3 聲明示例:
#import <UIKit/UIKit.h>
@interface TMAddCategoryViewController ()
@property (nonatomic, strong) NSMutableArray *dataSource;
@end
2.4 存取方法:編譯器會自動聲明和實現
- @property會讓編譯器自動聲明相應的實例變量和存取方法,并實現存取方法。除非你用其它關鍵詞修飾,專門告訴編譯器做什么其它特殊處理。
- 即使自動生成存取方法,遇到一些需求時,你也可以再自己重寫存取方法。一般添加數據模型示例對象的時候,喜歡重寫getter方法,設置一些默認值,這種叫懶加載。
- 有一些例外,不會自動生成存取方法:
- 同時重寫了getter setter
- 重寫只讀屬性的 getter
- 使用了@dynamic
- @protocol 中定義的屬性
- category 中定義的屬性
- 重載的屬性:當你在子類中重載父類的屬性,你必須用 @synthesize 手動合成
2.5 示例:重寫getter
- (NSMutableArray *)dataSource
{
if (!_dataSource) {
_dataSource = [NSMutableArray array];
RLMResults *results = [[TMDataBaseManager defaultManager] queryAddCategorysWithPaymentType:self.paymentType];
for (TMAddCategory *category in results) {
[_dataSource addObject:category];
}
}
return _dataSource;
}
注:RLMResults是第三方數據庫框架Realm中的一個類名
2.6 示例:重寫setter
- (void)setDataSource:(id<iCarouselDataSource>)dataSource
{
if (_dataSource != dataSource)
{
_dataSource = dataSource;
if (_dataSource)
{
[self reloadData];
}
}
}
2.7 重寫setter和getter導致的特別情況:
@property聲明的屬性,編譯器是否會合成存取方法和成員變量有如下三種特別情況
- 若手動實現了setter方法,編譯器就只會自動生成getter方法
- 若手動實現了getter方法,編譯器就只會自動生成setter方法
- 若同時手動實現了setter和getter方法,編譯器就不會自動生成不存在的成員變量 。
2.8 編譯器自動實現的存取方法有什么弱點?
- @property只會生成最簡單的getter/setter方法,而不會進行數據判斷
2.9 指定所生成的方法的方法名稱
- getter=你定制的getter方法名稱
- setter=你定義的setter方法名稱(注意setter方法必須要有 :)
- 示例:
@property(getter=isMarried)BOOL married;
通常BOOL類型的屬性的getter方法要以is開頭
2.10 繼承特性:
- 父類聲明在頭文件 中的屬性,子類無法繼承這些屬性聲明的實例變量,只能看到屬性自動生成的存取方法。
3. 修飾詞:@synthesize 與 @dynamic
修飾詞:告訴編譯器是否或怎樣自動給屬性生成存取方法
@property有兩個對應的修飾詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;
。 顯然,這兩個修飾的功能是互斥的。
3.1 @synthesize 與 @dynamic
3.1.1 位置
@dynamic或者@synthesize,寫在.m文件的@implementation中。
3.1.2 功能區分
理解這兩個修飾詞的功能,可以先看看這兩個單詞的意思。
- synthesize 與 dynamic 英文意思
- synthesize ['s?nθ?sa?z] v. 合成;綜合
- dynamic [da?'n?m?k] adj. 動態的;動力的;動力學的;有活力的
3.1.3 單詞區分
單詞混淆: 多線程的概念里面有個關鍵詞@synchronized跟@synthesize容易視覺混淆,這里也看看兩個單詞的意思。
- synthesize 與 synchronized 單詞比較
- synthesize ['s?nθ?sa?z] v. 合成;綜合
- synchronized ['s??kr?na?zd] adj. 同步的;同步化的 v. 使協調(synchronize的過去分詞);同時發生;校準
3.2 @synthesize
3.2.1 介紹
定義屬性后,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做自動合成 (autosynthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter、setter 之外,編譯器還要自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線,以此作為實例變量的名字。
3.2.2 用法
- 多個屬性可以通過一行@synthesize搞定,多個屬性之間用逗號連接
- 可以在類的實現代碼里通過@synthesize語法來指定實例變量的名字。
@synthesize name = realName;
對于上面的實例變量則為生成的是realName而不是name,方法也對應改變。定義的實例變量是根據@synthesize name = realName;
來定的。用的比較多的情況是:
@synthesize name = _name;
上述代碼的意思是,在@property 聲明的name,在setter/getter
方法中使用NSObject * _name;
這個實例變量來賦值與返回。
3.2.3 三種寫法比較
@synthesize age = _age;
- setter和getter實現中會訪問成員變量_age
- 如果成員變量_age不存在,就會自動生成一個@private的成員變量_age
@synthesize age;//等效下面
@synthesize age = age;
- setter和getter實現中會訪問@synthesize后同名成員變量age
- 如果成員變量age不存在,就會自動生成一個@private的成員變量age
3.2.4 用法場景
當你在子類中重載了父類中的屬性,你必須使用@synthesize來手動合成實例變量。
3.3 @dynamic
3.3.1 介紹
- @dynamic告訴編譯器:屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對于readonly的屬性只需提供getter即可)。
3.3.2 崩潰
- 假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var = someVar,由于缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由于缺getter方法同樣會導致崩潰。
- 編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
3.4. 注意的事
- @synthesize 僅僅是一個 clang 的 Objective-C 語言擴展 (autosynthesis of properties), 然后clang恰好是 Xcode 的默認編譯器.
- 如果編譯器換成了 gcc, 那么這個特性也就不復存在了。
4. 其它修飾詞
很多人講這些修飾詞的時候,喜歡從字面或者定義的角度介紹它們間的區別。這篇文章,我們從修飾詞對setter方法的影響直接展示區別。
retain、assign、copy、weak、strong、nonatomic、atomic、readonly、readwrite
假設為了修飾一個屬性nameStr,代碼如下:
#import <UIKit/UIKit.h>
@interface CMViewController ()
@property (nonatomic, strong) NSString *nameStr;
@end
其中,當括號內的修飾詞(nonatomic, strong)換成下面各種修飾詞的時候,分別分析一下setter方法(有些修飾詞修飾字符串并不合適,但這里僅為分析區別)。
4.1 assign
4.1.1 基本特性
- assign (默認):直接賦值,不更改引用計數。
4.1.2 對setter的影響
- assign修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
nameStr = newValue;
}
4.1.3 使用場景
- 常用于基本數據類型(NSInteger)和C數據類型(int、float、double、char以及id類型。
- 這個修飾符不會牽涉到內存管理,但是如果是對象類型,可能會導致內存泄漏或者EXC_BAD_ACCESS錯誤。
- 除了assign以外的其他修飾符,是必須用于修飾OC對象的。
4.1.4 危險場景
- assign修飾的對象銷毀后不會把該對象的指針置nil。對象已經被銷毀,但指針還在癡癡的指向它,這就成了野指針,這是比較危險的。所以assign修飾的OC屬性是非常危險的,比如,一些老的第三方框架用assign修飾的delegate屬性經常會導致崩潰。
4.2 retain
4.2.1 基本特性
- retain: 指針拷貝。指針拷貝后,地址不變,內容不變,引用計數器加1。
4.2.2 對setter的影響
- retain修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
if (nameStr !=newValue){
[nameStr release]
nameStr = [newValue retain];
}
}
4.2.3 使用場景
- 適用NSObject和其子類。MRC下,用于修飾多數的OC類型的對象。ARC下,一般功能被strong取代。
- 不能用于基本的數據類型或者Core Foundation的對象(retain操作會使對象的引用計數器加1,但是基本的數據類型或者Core Foundation對象壓根就沒有引用計數器,所以無法進行retain操作。換言之:基本數據類型或者CF不是指針,不是指針就無法進行retain操作。對象即指針嘛)。
4.3 copy
4.3.1 基本特點
- copy是內容拷貝。釋放舊對象,然后建立一個索引計數為1的對象。
- strong修飾的屬性在賦值時不會調用copy,而copy修飾的屬性在賦值相當于自動多調用了一次copy方法。
4.3.2 對setter的影響
- copy修飾詞對setter的影響:
- (void) setName:(NSString *)newValue{
if (nameStr !=newValue){
[nameStr release]
nameStr = [newValue copy];
}
}
4.3.3 使用場景
copy的使用場景為,實現了NSCopying protocol的類,我想獲取它的值,但是我又不想在原對象上改變,于是深賦值一份新的值給你,讓你來自由操作。
- 用于修飾block。
- 用于含有可深拷貝的mutable子類的類,如NSString,NSArray,NSSet,NSDictionary,NSData的,NSCharacterSet,NSIndexSet。
- 但是NSMutableArray這樣的不可以,Mutable的不能用copy,不然初始化會有問題。
4.4 weak
weak、strong是ARC出現后才出現的概念,但這并不代表weak、strong這兩個修飾都不能在MRC模式下使用。事實上,strong在MRC模式依舊可使用。
4.4.1 基本特性
- weak 用來修飾強引用的屬性,類似于對應原來的assign。
- weak是一種弱引用,并不會使對象的引用計數加1,可以避免循環引用的問題。
- 不保留傳入的對象。如果該對象被釋放,那么相應的實例變量會被自動賦為nil,不會變為懸空指針(也稱野指針)。懸空指針指向的是不再存在的對象,向懸空指針發送消息通常會導致程序崩潰。
4.4.2 兩種模式下
- MRC模式
- weak: MRC模式下無法使用
- ARC模式
- weak: 弱引用,不會使對象的引用計數器加1。
4.4.3 與assign的區別
- weak修飾的對象銷毀的時候,指針會自動設置為nil。而assign不會。
- assign可以用于非OC對象,而weak必須用于OC對象。
4.4.4 使用場景
- 用于修飾UI控件。
- UI控件拖到xib/Storyboard后,系統自動為控件賦了strong,所以拖到代碼就用weak就行了。
- 代理屬性。
@property (nonatomic, weak) id delegate; // 修飾代理屬性
4.5.5 對setter的影響
- weak修飾詞對setter的影響:假設nameStr和newValue都是用weak修飾的屬性
[nameStr release]
nameStr = newValue;
4.5 strong
4.5.1 基本特性
strong 用來修飾強引用的屬性,類似于對應原來的retain。
4.5.2 兩種模式
- MRC模式
- strong: 與retain等價
- ARC模式
- strong: 強引用(它使對象的引用計數加1)
4.5.3 使用場景
- 當要保住某個對象的命,讓這個對象可以用于其他的方法時(即在某段時間內要經常用到這個對象,又不想每次用到這個對象都要重新alloc),此時你要把這個對象變為強指針,即變為strong,讓strong強引用著這個對象,使這個對象不會被釋放。
- 用于修飾NSMutableArray,NSMutableDictionary等copy無法修飾的屬性。
- 一般的指針變量默認就是strong類型的,所以我們對于strong變量不加__strong修飾。
NSString *name = self.nameField.text; // 等價
__strong NSString *name = self.nameField.text; // 等價
4.5.4 對setter的影響
- strong修飾詞對setter的影響:假設nameStr和newValue都是用strong修飾的屬性
[newValue retain]
[nameStr release]
nameStr = newValue;
4.6 讀寫屬性
讀寫性修飾符——readwrite、readonly
4.6.1 readwrite
- readwrite(默認): 可讀可寫(系統自動創建getter 和 setter 方法)
4.6.1 readonly
- readonly: 只讀,系統只會生成 getter方法
4.7 原子屬性
4.7.1 atomic
- 1.原子屬性,聲明的屬性默認就是atomic.所以底層默認為屬性的setter方法加鎖,目的就是防止多(條)線程訪問同一個內存地址,造成數據錯誤。
- 2.在多線程環境下,原子操作非常有必要,因為它能提供多線程安全,如果沒有原子操作,可能引起異常。--->線程保護
- 3.需要消耗大量的資源。
4.7.2 nonatomic
- 1.非原子屬性,不會為setter方法加鎖。
- 2.沒有涉及多線程的編程時,用nonatomic。
- 3.不會消耗大量的資源,所以會提高性能。
5. @property的幾個例子
@property (nonatomic, readonly) CGFloat itemWidth;
@property (nonatomic, assign) double brightness;
@property (nonatomic, assign, getter=isOpenMenu) BOOL openMenu;
@property (nonatomic, strong) UILabel *newsBooksLabel;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, weak) IBOutlet UIButton *nextButton;
@property (nonatomic, copy) void (^cancelBtnBlock)();
@property (nonatomic, weak) id<TMSideCellDelegate> sideCellDelegate;