1. 程式人生 > 其它 >iOS學習——屬性引用self.xx與_xx的區別

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的功能主要有三個作用:

  1. 生成了成員變數get/set方法的宣告
  2. 生成了私有的帶下劃線的的成員變數因此子類不可以直接訪問,但是可以通過get/set方法訪問。那麼如果想讓定義的成員變數讓子類直接訪問那麼只能在.h檔案中定義成員變量了,因為它預設是@protected
  3. 生成了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中點表示式(.)其實就是呼叫物件的settergetter方法的一種快捷方式,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的地方就會報錯。