iOS學習——屬性引用self.xx與_xx的區別
在iOS開發過程中,我們用@proprety宣告一個屬性後,在程式碼中我們可以用self.xx與_xx來獲取到這個屬性。但是一直有一個疑惑,那就是這兩個之間有什麼區別呢?最初我一直覺得這兩個之間沒什麼區別的,直到有一次,我發現自己明明對宣告的屬性進行了賦值,但是在使用_xx引用時發現為nil,這才引起我的注意。所以,今天在這裡對這個問題進行統一的一個說明和學習。
1 @property 與 @synthesize
在說self.xx與_xx之前,我們先了解一下@property 以及 @synthesize之間的區別和聯絡,說到@property 以及 @synthesize,我們就不得不提到iOS中 成員變數和屬性 之間的區別和聯絡了。
接觸iOS的人都知道,@property
宣告的屬性預設會生成一個_型別的成員變數,同時也會生成setter/getter
方法。 但這只是在iOS5之後,蘋果推出的一個新機制。看老程式碼時,經常看到一個大括號裡面定義了成員變數,同時用了@property宣告,而且還在@implementation中使用@synthesize
方法,就像下面的程式碼這樣:
@interface ViewController () { // 1.宣告成員變數 NSString *myString; } //2.在用@property @property(nonatomic, copy) NSString *myString; @end @implementation ViewController //3.最後在@implementation中用synthesize生成set方法 @synthesize myString; @end
其實,發生這種狀況根本原因是蘋果將預設編譯器從GCC轉換為LLVM(low level virtual machine
),才不再需要為屬性宣告例項變量了。在沒有更改之前,屬性的正常寫法需要 成員變數 + @property + @synthesize 成員變數
三個步驟。
如果我們只寫成員變數+ @property
:
@interface GBViewController :UIViewController
{
NSString *myString;
}
@property (nonatomic, strong) NSString *myString;
@end
//編譯時會報警告: Autosynthesized property 'myString' will use synthesized instance variable '_myString', not existing instance variable 'myString'
但更換為LLVM之後,編譯器在編譯過程中發現沒有新的例項變數後,就會生成一個下劃線開頭的例項變數。因此現在我們不必在宣告一個例項變數。(注意
:==是不必要,不是不可以==) 當然我們也熟知,@property
宣告的屬性不僅僅預設給我們生成一個_型別的成員變數,同時也會生成setter/getter
方法。在.m
檔案中,編譯器也會自動的生成一個成員變數_myString
。那麼在.m檔案中可以直接的使用_myString成員
變數,也可以通過屬性self.myString
.都是一樣的。注意這裡的self.myString
其實是呼叫的myString
屬性的setter/getter
方法。
此外,如果我們再最新的程式碼中宣告一個成員變數,如下程式碼所示,那麼我們只是聲明瞭一個成員變數,並沒有setter/getter
方法。所以訪問成員變數時,可以直接訪問name
,也可以像C++一樣用self->name
來訪問,但絕對不能用self.name
來訪問。
@interface MyViewController :UIViewController
{
NSString *name;
}
@end
從Xcode4.4以後,即iOS的@property已經獨攬了@synthesize的功能主要有三個作用:
- 生成了成員變數get/set方法的宣告
- 生成了私有的帶下劃線的的成員變數因此子類不可以直接訪問,但是可以通過get/set方法訪問。那麼如果想讓定義的成員變數讓子類直接訪問那麼只能在.h檔案中定義成員變量了,因為它預設是@protected
- 生成了get/set方法的實現
值得注意的是:
- 如果已經手動實現了get和set方法(兩個都實現)的話Xcode不會再自動生成帶有下劃線的私有成員變量了
- 因為xCode自動生成成員變數的目的就是為了根據成員變數而生成get/set方法的,但是如果get和set方法缺一個的話都會生成帶下劃線的變數
2 self.xx與_xx
上面我們說到了屬性與成員變數、@property 以及 @synthesize之間的聯絡與區別。同時,我們提到了self.xx和_xx的一點區別,其中self.xx是呼叫的xx屬性的get/set方法,而_xx則只是使用成員變數_xx,並不會呼叫get/set方法。兩者的更深層次的區別在於,通過存取方法訪問比直接訪問多做了一些其他的事情(例如記憶體管理,複製值等),例如如果屬性在@property中屬性的修飾符有retain,那麼當使用self.xx的時候相應的屬性的引用計數器由於生成了setter方法而進行加1操作,此時的retaincount為2。
- 擴充套件:很多人覺得OC中的點語法比較奇怪,實際是OC設計人員有意為之。
-
點表示式(.)
看起來與C語言中的結構體訪問以及java語言彙總的物件訪問有點類似,如果點表示式出現在等號=
左邊,呼叫該屬性名稱的setter
方法。如果點表示式出現在=
右邊,呼叫該屬性名稱的getter
方法。 - OC中
點表示式(.)
其實就是呼叫物件的setter
和getter
方法的一種快捷方式,self.myString = @"張三";
實際就是[self setmyString:@"張三"];
最後說一下容易出現的問題的地方,根據我個人的經驗,最容易出問題的地方就是對屬性xx或成員變數_xx的初始化的地方和呼叫時機,直接通過例子來看,我們將屬性和例項變數的初始化放在重寫的get方法中,於是我們在 - (void)viewDidLoad 中使用_invoiceInfoImageView來進行佈局時,實際上因為在這之前也沒有呼叫invoiceInfoImageView的get方法,所以此時invoiceInfoImageView的值其實為nil,介面上是空白的。
#import "InvoiceTitleInfoViewController.h"
@interface InvoiceTitleInfoViewController ()
//定義屬性invoiceInfoImageView
@property (strong, nonatomic) UIImageView *invoiceInfoImageView;
@end
@implementation InvoiceTitleInfoViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"發票抬頭";
//用_xx來呼叫例項變數_invoiceInfoImageView,此時由於沒有呼叫get方法進行初始化,因此此時_invoiceInfoImageView的值為nil
[self.view addSubview:_invoiceInfoImageView];
WEAKSELF
[_invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
}];
}
//初始化屬性invoiceInfoImageView,其實這就是invoiceInfoImageView的get方法
- (UIImageView *)invoiceInfoImageView{
if (!_invoiceInfoImageView) {
_invoiceInfoImageView = [[UIImageView alloc] init];
_invoiceInfoImageView.image = [UIImage imageNamed:@"invoice_title_info"];
}
return _invoiceInfoImageView;
}
如果我們在 使用self.xx來呼叫變數,則會呼叫invoiceInfoImageView的get方法,進行初始化,介面佈局將會顯示我們想要的圖片。此外,如果我們再使用_xx之前用self.xx呼叫過變數invoiceInfoImageView,則同樣會呼叫其get方法從而觸發invoiceInfoImageView的初始化,這樣也不會影響佈局。
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"發票抬頭";
//用self.xx來呼叫invoiceInfoImageView
[self.view addSubview:self.invoiceInfoImageView];
WEAKSELF
[self.invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"發票抬頭";
//先用self.invoiceInfoImageView觸發get方法進行初始化,這樣_invoiceInfoImageView的值被初始化後不為nil
self.invoiceInfoImageView;
[self.view addSubview:_invoiceInfoImageView];
WEAKSELF
[_invoiceInfoImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(weakSelf.view).mas_offset(0.0f);
make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f);
make.right.bottom.mas_equalTo(weakSelf.view).mas_offset(-15.0f);
}];
}
還有一點值得注意的就是我們前面提到過的,如果我們同時手動重寫了一個屬性的get和set方法的話,Xcode不會再自動生成帶有下劃線的私有成員變量了。如下圖所示,在我們只定義了get方法時一切都沒有問題,但是一旦我們又重寫set方法,會發現用到_xx的地方就會報錯。