天天看點

IOS開發之property詳解

之前很多網友對我翻譯的教程中的property的使用感到有些迷惑不解,搞不清楚什麼時候要release,什麼時候要self.xxx = nil;同時對于objective-c的記憶體管理以及cocos2d的記憶體管理規則不夠清楚。本文主要講解objc裡面@property,它是什麼,它有什麼用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter這些關鍵字有什麼用,什麼時候使用它們。至于objc的記憶體管理和cocos2d的記憶體管理部分,接下來,我會翻譯ray的3篇教程,那裡面再和大家詳細讨論。今天我們的主要任務是搞定@property。

  學過c/c++的朋友都知道,我們定義struct/class的時候,如果把通路限定符(public,protected,private)設定為public的話,那麼我們是可以直接用.号來通路它内部的資料成員的。比如

  class test

  {

  public:

  int i;

  float f;

  };

  我在main函數裡面是可以通過下面的方式來使用這個類的:(注意,如果在main函數裡面使用此類,除了要包含頭檔案以外,最重要的是記得把main.m改成main.mm,否則會報一些奇怪的錯誤。是以,任何時候我們使用c++,如果報奇怪的錯誤,那就要提醒自己是不是把相應的源檔案改成.mm字尾了。其它引用此類的檔案有時候也要改成.mm檔案)

  //in main.mm

  test test;

  test.i =1;

  test.f =2.4f;

  nslog(@"test.i = %d, test.f = %f",test.i,  test.f);

  但是,在objc裡面,我們能不能這樣做呢?請看下面的代碼:(建立一個objc類,命名為baseclass)

  //in baseclass.h

  @interface baseclass : nsobject{

  @public

  nsstring *_name;

  }

  接下來,我們在main.mm裡面:

  baseclass *base= [[baseclass alloc] init];

  base.name =@"set base name";

  nslog(@"base class's name = %@", base.name);

  不用等你編譯,xcode4馬上提示錯誤,請看截圖:

IOS開發之property詳解

  請大家注意看出錯提示“property 'nam' not found on object of type baseclass*",意思是,baseclass這類沒有一個名為name的屬性。即使我們在頭檔案中聲明了@public,我們仍然無法在使用baseclass的時候用.号來直接通路其資料成員。而@public,@protected和@private隻會影響繼承它的類的通路權限,如果你使用@private聲明資料成員,那麼在子類中是無法直接使用父類的私有成員的,這和c++,java是一樣的。

  既然有錯誤,那麼我們就來想法解決啦,編譯器說沒有@property,那好,我們就定義property,請看代碼:

  @property(nonatomic,copy) nsstring *name;

  //in baseclass.m

  @synthesize name = _name;

  現在,編譯并運作,ok,很好。那你可能會問了@prperty是不是就是讓”."号合法了呀?隻要定義了@property就可以使用.号來通路類的資料成員了?先讓我們來看下面的例子:

@interface baseclass : nsobject{

@public

nsstring *_name;

}

//@property(nonatomic,copy) nsstring *name;

-(nsstring*) name;

-(void) setname:(nsstring*)newname;

我把@property的定義注釋掉了,另外定義了兩個函數,name和setname,下面請看實作檔案:

//@synthesize name = _name;

-(nsstring*) name{

return _name;

-(void) setname:(nsstring *)name{

if (_name != name) {

[_name release];

_name = [name copy];

現在,你再編譯運作,一樣工作的很好。why?因為我剛剛做的工作和先前聲明@property所做的工作完全一樣。@prperty隻不過是給編譯器看的一種指令,它可以編譯之後為你生成相應的getter和setter方法。而且,注意看到面property(nonatomic,copy)括号裡面這copy參數了嗎?它所做的事就是

  _name = [name copy];

  如果你指定retain,或者assign,那麼相應的代碼分别是:

  //property(retain)nsstring* name;

  _name = [name retain];

  //property(assign)nsstring* name;

  _name = name;

  其它講到這裡,大家也可以看出來,@property并不隻是可以生成getter和setter方法,它還可以做記憶體管理。不過這裡我暫不讨論。現在,@property大概做了件什麼事,想必大家已經知道了。但是,我們程式員都有一個坎,就是自己沒有完全吃透的東西,心裡用起來不踏實,特别是我自己。是以,接下來,我們要詳細深挖@property的每一個細節。

  首先,我們看atomic 與nonatomic的差別與用法,講之前,我們先看下面這段代碼:

  @property(nonatomic, retain) uitextfield *username;    //1

  @property(nonatomic, retain,readwrite) uitextfield *username;  //2

  @property(atomic, retain) uitextfield *username;  //3

  @property(retain) uitextfield *username;  //4

  @property(atomic,assign) int i;         // 5

  @property(atomic) int i;         //6

  @property int i;               //7

  請讀者先停下來想一想,它們有什麼差別呢?

  上面的代碼1和2是等價的,3和4是等價的,5,6,7是等價的。也就是說atomic是預設行為,assign是預設行為,readwrite是預設行為。但是,如果你寫上@property(nontomic)nsstring *name;那麼将會報一個警告,如下圖:

IOS開發之property詳解

  因為是非gc的對象,是以預設的assign修飾符是不行的。那麼什麼時候用assign、什麼時候用retain和copy呢?推薦做法是nsstring用copy,delegate用assign(且一定要用assign,不要問為什麼,隻管去用就是了,以後你會明白的),非objc資料類型,比如int,float等基本資料類型用assign(預設就是assign),而其它objc類型,比如nsarray,nsdate用retain。

  在繼續之前,我還想補充幾個問題,就是如果我們自己定義某些變量的setter方法,但是想讓編譯器為我們生成getter方法,這樣子可以嗎?答案是當然可以。如果你自己在.m檔案裡面實作了setter/getter方法的話,那以翻譯器就不會為你再生成相應的getter/setter了。請看下面代碼:

//代碼一:

@property(nonatomic,copy,readonly) nsstring *name;  //這裡使用的是readonly,所有會聲明geter方法

//代碼二:

@property(nonatomic,copy,readonly) nsstring *name;   //這裡雖然聲明了readonly,但是不會生成getter方法,因為你下面自己定義了getter方法。

-(nsstring*) name;   //getter方法是不是隻能是name呢?不一定,你打開foundation.framework,找到uiview.h,看看裡面的property就明白了)

//代碼三:

@property(nonatomic,copy,readwrite) nsstring *name;  //這裡編譯器會我們生成了getter和setter

//代碼四:

@property(nonatomic,copy) nsstring *name;  //因為readwrite是預設行為,是以同代碼三

  上面四段代碼是等價的,接下來,請看下面四段代碼:

@synthesize name = _name;  //這句話,編譯器發現你沒有定義任何getter和setter,是以會同時會你生成getter和setter

@synthesize name = _name;  //因為你定義了name,也就是getter方法,是以編譯器隻會為生成setter方法,也就是setname方法。

nslog(@"name");

@synthesize name = _name;   //這裡因為你定義了setter方法,是以編譯器隻會為你生成getter方法

nslog(@"setname");

@synthesize name = _name;  //這裡你自己定義了getter和setter,這句話沒用了,你可以注釋掉。

  上面這四段代碼也是等價的。看到這裡,大家對property的作用相信會有更加進一步的了解了吧。但是,你必須小心,你如果使用了property,而且你自己又重寫了setter/getter的話,你需要清楚的明白,你究竟幹了些什麼事。别寫出下面的代碼,雖然是合法的,但是會誤導别人:

//baseclass.h

nsarray *_names;

@property(nonatomic,assgin,readonly) nsarray *names;  //注意這裡是assign

-(void) setnames:(nsarray*)names;

//baseclass.m

@implementation baseclass

@synthesize names = _names;

-(nsarray*) names{

nslog(@"names");

return _names;

-(void) setnames:(nsarray*)names{

nslog(@"setnames");

_name = [name retain];  //你retain,但是你不覆寫這個方法,那麼編譯器會生成setnames方法,裡面肯定是用的assign

  當别人使用@property來做記憶體管理的時候就會有問題了。總結一下,如果你自己實作了getter和setter的話,atomic/nonatomic/retain/assign/copy這些隻是給編譯的建議,編譯會首先會到你的代碼裡面去找,如果你定義了相應的getter和setter的話,那麼好,用你的。如果沒有,編譯器就會根據atomic/nonatomic/retain/assign/copy這其中你指定的某幾個規則去生成相應的getter和setter。

  好了,說了這麼多,回到我們的正題吧。atomic和nonatomic的作用與差別:

  如果你用@synthesize去讓編譯器生成代碼,那麼atomic和nonatomic生成的代碼是不一樣的。如果使用atomic,如其名,它會保證每次getter和setter的操作都會正确的執行完畢,而不用擔心其它線程在你get的時候set,可以說保證了某種程度上的線程安全。但是,我上網查了資料,僅僅靠atomic來保證線程安全是很天真的。要寫出線程安全的代碼,還需要有同步和互斥機制。

  而nonatomic就沒有類似的“線程安全”(我這裡加引号是指某種程度的線程安全)保證了。是以,很明顯,nonatomic比atomic速度要快。這也是為什麼,我們基本上所有用property的地方,都用的是nonatomic了。

  還有一點,可能有讀者經常看到,在我的教程的dealloc函數裡面有這樣的代碼:self.xxx = nil;看到這裡,現在你們明白這樣寫有什麼用了吧?它等價于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那麼就會這樣,如果不是,就對号入座吧)。

  因為nil可以給它發送任何消息,而不會出錯。為什麼release掉了還要指派為nil呢?大家用c的時候,都有這樣的編碼習慣吧。

  int* arr = new int[10];    然後不用的時候,delete arr; arr = null;  在objc裡面可以用一句話self.arr = nil;搞定。

最新内容請見作者的github頁:http://qaseven.github.io/