1. 程式人生 > >大括號宣告變數和@property宣告變數的那些事

大括號宣告變數和@property宣告變數的那些事

原文作者對LLVM 有個錯誤的說法,LLVM並不是(low level virtual machine)的縮寫,而是全名就是叫LLVM(LLVM的專案是一個模組化和可重複使用的編譯器和工具技術的集合)

歷史由來:

接觸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,才不再需要為屬性宣告例項變量了。

在沒有更改之前,屬性的正常寫法需要成員變數+ @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方法。這與C++中點的使用是有區別的,C++中的點可以直接訪問成員變數(也就是例項變數)。

例如在oc中有如下程式碼

@interface MyViewController :UIViewController
{
    NSString *name;
}
@end

在這段程式碼裡面只是聲明瞭一個成員變數,並沒有setter/getter方法。所以訪問成員變數時,可以直接訪問name,也可以像C++一樣用self->name來訪問,但絕對不能用self.name來訪問。

  • 擴充套件:很多人覺得OC中的點語法比較奇怪,實際是OC設計人員有意為之。
  • 點表示式(.)看起來與C語言中的結構體訪問以及java語言彙總的物件訪問有點類似,如果點表示式出現在等號  左邊,呼叫該屬性名稱的setter方法。如果點表示式出現在右邊,呼叫該屬性名稱的getter方法。
  • OC中點表示式(.)其實就是呼叫物件的settergetter方法的一種快捷方式,self.myString = @"張三";實際就是[self setmyString:@"張三"];

首先我們要明白,@synthesize 生成了setter/getter方法。 
雖然現在直接使用@property時,編譯器會自動為你生成以下劃線開頭的例項變數_myString,不需要自己手動再去寫例項變數。而且也不在.m檔案中通過@synthesize myString;生成setter/getter方法。但在看老程式碼的時候,我們依舊可以看到有人使用成員變數+ @synthesize 成員變數的形式。

那麼問題來了: 
我們能否認為新編譯器LLVM下的@property == 老編譯器GCC的 成員變數+ @property + @synthesize 成員變數呢?

答案是否定的。 
因為成員變數+ @property + @synthesize 成員變數的形式,編譯器不會幫我們生成_成員變數,因此不會操作_成員變數了; 
同時@synthesize 還有一個作用,可以指定與屬性對應的例項變數, 
例如@synthesize myString = xxx; 
那麼self.myString其實是操作的例項變數xxx,而非_String了。  

三、類別中的屬性property

  類與類別中新增的屬性要區分開來,因為類別中只能新增方法,不能新增例項變數。經常會在ios的程式碼中看到在類別中新增屬性,這種情況下,是不會自動生成例項變數的。比如在:UINavigationController.h檔案中會對UIViewController類進行擴充套件

@interface UIViewController (UINavigationControllerItem)
@property(nonatomic,readonly,retain) UINavigationItem *navigationItem;
@property(nonatomic) BOOL hidesBottomBarWhenPushed;
@property(nonatomic,readonly,retain) UINavigationController *navigationController;
@end

  這裡新增的屬性,不會自動生成例項變數,這裡新增的屬性其實是新增的getter與setter方法。

  注意一點,匿名類別(匿名擴充套件)是可以新增例項變數的,非匿名類別是不能新增例項變數的,只能新增方法,或者屬性(其實也是方法)。

四、成員變數、例項變數、屬性變數的聯絡

@interface MyViewController :UIViewControlle
{
UIButton *yourButton;
int count;
id data;
}
@property (nonatomic, strong) UIButton *myButton;
@end

   在{   } 中所宣告的變數都為成員變數。 所以yourButton、count、data都是成員變數。既然如此,例項變數又是什麼意思呢?

  例項變數本質上就是成員變數,只是例項是針對類而言,例項是指類的宣告。{   }中的yourButton就是例項變數。id 是OC特有的類,本質上講id等同於(void *)。所以id data屬於例項變數。

  成員變數用於類內部,無需與外界接觸的變數。因為成員變數不會生成set、get方法,所以外界無法與成員變數接觸。根據成員變數的私有性,為了方便訪問,所以就有了屬性變數。屬性變數的好處就是允許讓其他物件訪問到該變數(因為屬性建立過程中自動產生了set 和get方法)。當然,你可以設定只讀或者可寫等,設定方法也可自定義。所以,屬性變數是用於與其他物件互動的變數。

  綜上所述可知:成員變數是定義在{}號中的變數,如果變數的資料型別是一個類則稱這個變數為例項變數。因為例項變數是成員變數的一種特殊情況,所以例項變數也是類內部使用的,無需與外部接觸的變數,這個也就是所謂的類私有變數。而屬性變數是用於與其他物件互動的變數。

  但是,現在大家似乎都不怎麼喜歡用成員變數來定義類的變數,都喜歡用屬性變數來定義類的變數。把需要與外部接觸的變數定義在.h檔案中,只在本類中使用的變數定義在.m檔案中。

首先來區別一下例項變數、成員變數的區別:

可以看到在介面 @interface 括號裡面的統稱為”成員變數”,例項變數是成員變數中的一種!

例項變數的英文翻譯是 Instance Variable (object-specific storage) 

例項的英文翻譯為Instance(manifestation  of a  class)  說的是“類的表現”,說明例項變數應該是由類定義的變數!

除去基本資料型別int float ....等,其他型別的變數都叫做例項變數。

**例項變數+基本資料型別變數=成員變數**

接下來說說屬性:

在@property(描述1 , 描述2 , 描述3)(class *) varName 裡面,有3個描述詞需要填寫(也可以不填寫取預設值)

1. nonatomic<-->atomic

2. readwrite<-->readonly

3. retain/copy/assign

首先來介紹一下:

retain:他指的是將某個記憶體區域的指標賦值給變數,同時把該記憶體區域的引用計數器加1.每執行一次,該記憶體區域的引用計數器就要加1,當該區域的引用計數器變為0的時候記憶體區域被釋放!

copy:它指的是將目標記憶體區域的值複製一份,然後開闢新的記憶體區域(新的指標)貼上這個值。同時變數被賦值為新記憶體區域的指標!

assign:它指的是,僅只把目標記憶體區域的指標賦值給變數,該記憶體區域的引用計數器不發生變化!

1、2兩點不做解釋,3中的retain、copy、assign都是指的,在自動生成setter函式的時候,編譯器需要識別個描述詞來生成對應的setter函式!需要注意的是,如果沒有加上該類的描述詞,系統預設該變數的setter方法採取assign的方式。

在標頭檔案中.h一般在{}裡面會有定義的例項變數  

示例:

.h

@property (automic,retain) NSString * abc;

.m

@sythesize abc;

//在寫了@sythesize abc;的情況下,系統不會自動生成例項變數“_abc”,直接通過變數名abc ,也就是直接使用變數名在賦值運算的時候(=號左邊),只是將記憶體區域的指標賦值給變數,相當於assgin.   如果是通過“點語句”self.abc= 來賦值,就要看在@property中定義的是copy、retain、assign哪一種了,如果沒有加上上述描述詞,就預設為assign。

//如果沒有寫@sythesize abc;  系統會預設自動在.h檔案{}中新增一個 不可見的 加“_”的成員變數(即使是變數名中本身就帶有“_”)

//括號裡面定義的都是成員變數(基本資料型別+類生成變數),裡面的變數可以在.m檔案中通過“變數名稱”、self->“變數名稱”直接訪問到括號裡面的變數,但是,這樣的賦值訪問只能是assign,原物件的引用計數器不會發生變化。

//[email protected] 變數名;[email protected] 變數名=_變數名;3.不寫@sythesize (一下提到的變數名都是指的在標頭檔案中@property 中定義的變數)

1.成員變數,例項變數通過“變數名”或者self->“變數名”直接訪問到,賦值(assign)。self.變數名 實現setter,getter方法。

2.成員變數,例項變數通過“_變數名”或者self->“_變數名”直接訪問到,賦值(assign)。self.變數名 實現setter,getter方法。

3.成員變數。例項變數(系統自動在原來變數名前加上“_”來生成的例項,成員變數),直接通過self->_變數名,或者變數名直接訪問到(assign)。self.變數名 實現setter,getter方法。

如果在標頭檔案中沒有通過@property定義的變數,但是在{}中有定義成員變數,在實現檔案中也也沒有@sythesize ,那麼可以直接通過self->“{}中的變數名”,或者直接使用“{}中的變數名”來訪問賦值,這樣的變數沒有定義setter,getter函式,只能是assign的方式賦值。

//再來分析一下@sythesize中的寫法,@sythesize abc 直接在.m檔案中使用self.abc可以呼叫成員變數的setter、getter函式,直接呼叫成員變數名稱abc就為訪問該變數的指標,對成員變數直接賦值等同於ASSIGN效果。