在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的地方就會報錯。
我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan