寫這篇博客是因為偶爾看到一篇文章,具體我忘了。大意就是說找一個靠譜的iOS開發人員,問他一個問題@property'的用法,就能大致知道他的水平。一開始我認為這是一個很低級的問題。然后我看了他大致看了一下他的問題。然后就有了接下來這篇文章。
首先我們創建一個CLT工程。然后創建一個ClassA,在.h文件中添加如下代碼
@interface ClassA : NSObject
{
int _num;
char _chr;
}
@end
如上所示,我們定義兩個成員變量.最近兩年學習iOS朋友估計對這種寫法比較陌生,莫慌,切聽我慢慢道來.
首先我們將這個聲明在main函數使用一下,大家就會發現其實這個時候我們的實例對象時不能訪問這兩個成員變量.
](http://upload-images.jianshu.io/upload_images/967672-3e1852c1847b2733.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這個時候有沒有想說一句,既然不能訪問,我們聲明他有神馬意義,完全沒用啊.原因是神馬那.
這句話原因應該很清楚了,這個時候我們發現原來,實例變量是protected屬性,原則上是不能訪問的.這時候有過面向對象變成的朋友會說,改成public屬性就可以了.沒錯,改為public屬性的確可以解決,但是這不是我今天要說的重點,有興趣的朋友可以去嘗試一下.
我們大家都應該知道面向對象的三大特征,封裝,繼承,多態.既然OC是一門面向對象編程的語言,我們我應該能夠想到用面向對象語言的特性去解決.沒錯,就是用數據的封裝去解決.有過編程經驗的朋友應該聽說過set,get方法(好像也叫屬性訪問控制器),他們是面向對象編程語言慣用的實例變量封裝方法.形式如下:
- (void)setNum:(int)num;
- (int)num;
上面兩個方法如果看不懂,兄弟,證明你學的有些問題,而且還不小.在這里我不跟大家詳談,如果不懂得朋友,可以看一些關于面向對象的書籍.至于實現,很簡單,不過我還是講他寫出來.
- (void)setNum:(int)num {
_num = num;
}
- (int)num {
return _num;
}
為什么我們這樣寫,我簡單也跟大家提一句,其實看到這兩個函數,大家應該都明白,提示我們就是講原來通過實例變量直接訪問實例變量屬性變成通過公共方法去訪問實例變量的屬性.
大家肯定會很納悶,今天你不是要說property,為啥主角到現在都沒出場那.ok,現在馬上出場.現在我們注釋掉原來的聲明方式.采用property聲明
這個時候
這個時候會出現以下情況:
1.在main函數中可以通過set,get方法訪問成員變量,但是不能直接訪問.
2.在類中可以直接訪問到相應的實例變量.
這個時候我們在來創建另外一個類,我們讓個這個類繼承與這個類,我們實現是一個共有方法,然后輸出num的值,這個時候我們會發現我們可以通過set和get方法可以訪問到num,但是不能直接通過變量名訪問.
通過以上的實際操作我們得出以下結論.
property聲明的變量屬性不是public.
property聲明的變量會自動生成相應的實例變量,set,get方法
在這里再跟大家多說一點,估計也有很多見到過這個東西。就是如下的形式
@synthesize num = _num;
對于上面這行代碼,《objective-c 2.0程序編程》有明確的說明,編譯器會自動生成相應的set,get方法,并且與成員變量(_變量名)相關聯。
話說如果到了這里你就覺得@property的使用就結束了,那我只能說一句,年輕人,現在知識個開頭而已。言歸正傳,
2property的內存管理
說到這里就要說下事例變量的屬性了(其實就是括號里面的內容)。說明一下,變量的屬性主要是和內存管理有關,強調一下,是主要是內存管理有關,不是全部
atomic(原子性) vs nonatomic(非原子性)【線程安全】
這主要是針對與多線程編程環境下來說的。怎么說那,假設我們現在多線程環境編程中,如果同時又兩個或者兩個以上的線程同時對一個屬性就行賦值,這個時候為了保證屬性調用前后的一致性,我們通常要做些多余的事,這就是傳說中的線程安全。也就是說線程安全就是為了保證在多線程環境中,有且僅有一個線程能對當前屬性進行set和get操作,其他線程必須等待其完成操作之后再進行操作。
回到咱們主題上,Objective-C中原子性就是為了保證線程安全而存在。如果當前的某一屬性的屬性為原子性,那么任何一個線程對其記性set和get方法時都會對當前的屬性進行加鎖和解鎖操作(Objective-C中保證線程安全的一種方法)。從而保證其在多線程編程環境的線程安全。通常情況下,我們不會涉及過多的線程安全,并且加鎖和解鎖操作也會造成相當多的資源開銷,所以我們一般都將屬性設置為非原子性。但是蘋果公司為了安全考慮出發,默認情況下,這個屬性是非原子性
readwrite(讀寫) vs readonly (只讀) 【訪問控制】
這兩個屬性相對還是比較好理解的。這屬性默認情況下是讀寫的,這就是為什么我們可以對實例變量進行取值和賦值的操作,其實質就是有set和get方法。通過這個說明相信聰明的你已經猜到只讀的含義和實質了。只讀的含義大家用心領悟一下,在這里我我說一下。其實就是就是只寫了get方法,沒有提供get方法。到這里不知道大家有沒有想過一個問題。為啥沒有只寫方法,想了半天,突然發現,只寫有不能讀有啥用啊
另外如果你不喜歡編譯器默認的set和get方法,你可以自定義set和get方法,聲明如下
@property (getter=isRunning, readonly) BOOL running;
為了操作的方便,蘋果屬性將讀寫屬性設置為實例變量的默認屬性
接下來是今天要說的重頭戲
strong (強引用) vs weak(弱引用) vs assign(賦值) vs copy(復制)
每一遍編程語言都無法繞開的深淵--內存管理。特別是在移動設備上這種內存資源相對短缺的設備上。當然這個也是有相對來說的,比如現在Android手機最大的運行內存已經可達到4G,呵呵噠...... 但是就目前iOS設備來說,內存資源還是比較稀缺的(貌似iPhone 7要擴容了)。
在大多數的面向對象的語言中都是通過垃圾回收,但是蘋果公司開發一套我認為相當流b的機制--對象擁有關系(object ownership)。專有的名詞沒找到,我就強行秀一波英語。大概就是說,當我們對一個對象就行操作時,我們必須確定他的存在,就是說我們必須擁有它。如果當前對象沒有擁有者,那么操作系統(不是編譯器)會在一個合適的時間(目前我也不確定,有種說法是兩個runloop切換期間)將其銷毀并釋放掉其所占內存。這個機制的實現依賴于ARC機制。至于ARC機制在這里我不多講,有一點跟大家說明,當對象擁有者增加時,當前對象的引用計數會+1,如果引用計數為0時,就是沒有對象擁有,那么這個對象就可能被釋放。
strong屬性就是表示我擁有當前對象,并且當前對象的引用計數+1.strong有一個好處就是說,我能夠確認當前對象的存在,因為只要我不消失,當前對象就不會被銷毀,即不會消失。這就保證了我能夠在需要的時候隨時訪問當前這個對象。
通過上面的介紹大家應該能推出來如果要釋放一個對象,必須是他的引用計數(擁有者的數量)為0.好大家看下面這張圖
簡單說明一下,對象A強應用對象B,同時對象B也強引用對象A,這時候大家想象一下,當我程序執行結束后,操作系統會怎樣釋放這兩個對象那,因為程序執行過程中,兩個對象的引用計數都不可能為0,因而都不會被釋放,但是當程序執行結束,會怎樣?沒錯,這個就是經典的循環引用計數問題,而他的直接后果就是鼎鼎大名的“內存泄漏”。當程序執行完成之后,操作系統不會釋放掉任何一方,從而導致兩者一直留在內存中,導致我們的內存越來越下。
很明顯這個不是我們想要的,但是以上的情況和多時候我們又無法避免。為了解決這個問題,另外一個屬性被發明出來,他就說weak,同樣咱們看一下模態圖
簡單說明,對象A此時仍然強引用對象B,而對象B弱引用對象A。細心同學發現此時對象B擁有對象A為虛線,并且對象A的引用計數并沒有增加。或許你會想是不是畫錯了。我可以告訴NO,這這你weak的精髓。簡單明了,當B釋放的時候,我的兩個對象都會被釋放掉,并且當前對象A的指針會被置為nil。弱引用的實質就是和strong一樣擁有當前對象,能夠對象當前對象進行操作,但是不使其引用計數增加。這樣就完美的解決了循環引用問題。給他家提個醒,不要亂用weak,可能會導致奇怪的bug。代理協議的實例對象通常設置為weak屬性
至于copy這個屬性可能是有人覺得比較茫然。如果一個屬性被設置為copy屬性時,對象記性讀寫操作,他獲得的是當前的對象的一份copy,而不是簡單對其進行應用。并且源對象的引用計數不會增加。通常經常下copy屬性主要用于源對象可能發生改變,而不像當前對象受其影響。不如說我們將一個NSMutableString類型的String賦值給NSString類型的對象,這個時候我們為了防止修改NSMutableString對象對當前對象影響。我們會考慮將其設置為copy。通常情況string對象和block對象會被設置為copy屬性。
總結一下,以上三個屬性都是針對于對象來說的,但是大家都知道Objective-C不是一門新的編程語言,它只是在C語言的基礎上加上了一層面向對象的特性。那么問題來了,C語言中有好多基礎類型,聲明這些屬性時我們怎樣設置的他的屬性?別急,不還有一個嗎?assign主要用來修飾Objective-C中基礎屬性。Objective-C支持64位以后全面更新了基礎屬性的定義。其實就是做了一些兼容64的修改,例如int -> NSInteger等。
當然還有一些其他的屬性,比如unsafe_unretained,這個跟weak有很多相似,當時不同的是他不會將當前的對象指針置為nil。如果你需要使用他,請確定當前環境不支持weak,因為從他名字就知道這貨不是很好。哈哈哈。
當然以上的所有都是針對于ARC機制下來說,對于老的開發程序員(就是MRC下開發的前輩們),還有retain等屬性。在這里我就不再展開來說。我表示我趕上了MRC的尾巴,但是當時沒有研究這個東東,感覺太坑了。就直接跳進了ARC的懷抱。現在想想,還是真是幸福啊。哈哈
參考文獻:
《Objective-C 2.0程序設計》 (美)Steophen G.Kochan 著
Ry`s Cocoa Tutorial http://rypress.com/tutorials/objective-c/properties
Objective-C Property Attributes https://realm.io/news/tmi-objective-c-property-attributes/
iOS-Blog http://www.ios-blog.co.uk/tutorials/objective-c/synthesize-vs-dynamic/
StackOverflow1 http://stackoverflow.com/questions/5170631/what-does-synthesize-window-window-do
StackOverflow2 http://stackoverflow.com/questions/8927727/objective-c-arc-strong-vs-retain-and-weak-vs-assign