原百度文庫鏈接:http://wenku.baidu.com/view/6786064fe518964bcf847c63.htmlhtml
PS:須要原文檔的能夠留郵箱發送!java
(我叫雷鋒,不要謝我)程序員
學習Objective-C入門教程express
1,前言編程
相信iPhone不久就要在國內發佈了,和咱們在國內能夠經過正規渠道買獲得的iPodTouch同樣,iPhone也是一個激動人心的產品。iPhone發佈的同時,基於iPhone的程序也像雨後春筍同樣在iTunes裏面冒出來。小程序
你未來也許會考慮買一個iPhone,體驗一下蘋果的富有創意的種種應用;你也許會考慮向iTunes的社區的全世界的人們展現一下你非凡的創意,固然也能夠經過你的創意獲得一些意想不到的收益。數組
OK,你也許火燒眉毛的準備開發了。可是先等一下,讓咱們回憶一下最初的電影是怎麼拍攝的。這個很重要,由於和iPhone的開發比較相似。瀏覽器
在最初由於器材比較原始,因此拍攝電影須要很高的技術,那個時候的電影的導演基本上是能夠熟練操做攝影器材的人。隨着器材的完善,使用也簡單起來。因而器材的使用不是決定一個電影的質量的惟一的因素,取而代之的是故事或者說電影的創意。安全
iPhone的開發也是這樣。固然從入門到掌握的過程來講任何事情都是開始比較難,隨着掌握的程度的加深,你將會以爲開發iPhone應用程序是一件簡單並且輕鬆的事情,到了那個時候,你的主要的制勝武器就不是開發技術,而是你的創意了。對於你來講,我在這裏寫的東西都是有關「攝影器材」也就是介紹如何使用iPhone的平臺來開發應用程序。網絡
iPhone的開發語言是Objective-C。Objective-C是進行iPhone開發的主要語言,掌握了Objective-C的基本語法以及數據結構以後,你須要熟悉一下iPhone的SDK。筆者很難作到在一篇文章裏面把全部的東西都介紹清楚,因此筆者打算分紅兩個主題,一個是Objective-C,一個是iPhone開發。
本系列將側重於Objective-C。固然,任何一種開發語言都沒法脫離於運行環境,Objective-C也不例外。因此在本系列當中也會穿插的介紹一些SDK裏面的一些特性,主要是數據結構方面,好比說NSString,NSArray等等。看到NSString,NSArray這些名詞,你也許會感到有些茫然,不過沒有關係,隨着本系列的深刻介紹,你會發現你很是喜歡這些東西。
1.1,誰會考慮閱讀本系列
若是你對iPhone感興趣,若是你考慮向全世界的人們展現你的創意,若是你有一顆好奇心,若是你打算經過開發iPhone程序謀生,若是你以爲蘋果比Windows酷,若是你認爲不懂蘋果的話那麼就有些不時尚的話,那麼能夠考慮閱讀本系列。
老手也能夠考慮花一點時間閱讀一下,能夠發帖子和筆者交流切磋。筆者發佈的文章屬於公益寫做,旨在爲你們介紹iPhone開發的一些基礎知識,若是能夠提供寶貴意見,筆者將不勝感激。
1.2,須要準備的東西
第一,你須要一臺蘋果電腦。固然這個不是必需的條件,若是你能夠在你的IntelPC上成功安裝MACOS的話,那麼請忽略這一條。
第二,你須要去蘋果網站上下載開發工具XCODE。注意,XCODE是徹底免費的,可是須要你去註冊一個帳號才能夠下載。因爲XCODE不時的在更新,因此若是你的MACOS不支持你下載的XCODE的話,那麼你也許須要考慮買一個最新的MACOS。
第三,你須要至少有C,C++,或者JAVA的背景知識。不過若是你沒有,那麼也不用擔憂,相信閱讀了筆者的文章以後應該也能夠掌握。
最後須要的東西就不是必須的了,固然有的話會更好一些。這些東西是,開發者帳戶(須要付費),iPhone手機(在部分國家能夠免費得到,可是中國會怎麼樣,筆者不是很清楚),iPodTouch(須要購買)。
1.3,關於筆者的寫做
筆者利用業餘時間進行寫做,因此沒法對文章發佈的時間表作出任何保證,還請各位讀者諒解。可是筆者會盡最大努力在短期以內完成寫做。
因爲筆者經驗才識所限,在本教程當中不免會遇到遺漏,錯誤甚至荒謬的地方,因此還請同窗們批評指正。
對於已經完成的章節,基於一些條件的改變或者勘誤,或者你們提出的意見,筆者也會考慮作出適當的修改。
在每個章節都會有代碼的範例,筆者注重闡述基本概念因此代碼不免會有不完整或者錯誤的地方,同窗們能夠任意的在本身的代碼中使用筆者所寫的代碼,可是筆者不承擔因爲代碼錯誤給同窗們帶來的損失。同窗們在閱讀本教程的時候,能夠直接下載範例代碼運行,可是爲了熟悉編碼的環境以及代碼的規範,筆者強烈建議同窗們按照教程本身親自輸入代碼。
Objective-C的概念比較多,並且不少概念都相互交叉。好比說講解概念A的時候,須要概念B的知識,講解概念B的時候須要概念C的知識,講解概念C的時候須要概念A。這樣就給本教程的寫做帶來了必定的麻煩,很明顯筆者沒法在某一個章節裏面把全部的概念都講述清楚,因此每一章都有側重點,你們在閱讀的時候須要抓住每一章的側重點,忽略一些和本章內容無關的新的概念和知識。
1.4,本系列的結構
第1章,也就是本章
第2章,從Hello,World!開始
第3章,類的聲明和定義
第4章,繼承
第5章,Class類型,選擇器Selector以及函數指針
第6章,NSObject的奧祕
第7章,對象的初始化以及實例變量的做用域
第8章,類方法以及私有方法
第9章,內存管理
第10章,到目前爲止出現的內存泄漏事件
第11章,字符串,數組以及字典
第12章,屬性
第13章,類目(Categories)
第14章,協議(Protocols)
第15章,Delegate
第16章,線程
第17章,文件系統
第18章,數據系列化以及保存用戶數據
第19章,網絡編程
第20章,XML解析
2,從Hello,World!開始
本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
如今筆者假設你們已經有了開發的環境。好了,咱們開始構築咱們的第一個程序。
在開始第一個程序以前,筆者須要提醒你們一下,若是手裏面有開發環境的話而且是第一次親密接觸Xcode的話,爲了能夠熟悉開發環境,強烈建議按照筆者的步驟一步一步的操做下去。
2.1,構築Hello,World
第一步,啓動Xcode。初次啓動的時候,也許會彈出一個「WelcometoXcode」的一個對話框。這個對話框和咱們的主題沒有關係,咱們能夠把它關掉。
第二步,選擇屏幕上部菜單的「File->NewProject」,出現了一個讓你選擇項目種類的對話框。你須要在對話框的左邊選擇「CommandLineUtility」,而後在右邊選擇「FoundationTool」,而後選擇「Choose...」按鈕。如圖2.1所示。
圖2-1,新建項目
注意也許有人會問,你不是要講解iPhone的開發,那麼爲何不選擇「iPhoneOS」下面的「Application」呢?
是這樣的,在這個系列當中,筆者主要側重於Objective-C的語法的講解,爲了使得講解簡單易懂,清除掉全部和要講解的內容無關的東西,因此筆者在這裏只是使用最簡單的命令行。
第三步,Xcode會提問你項目的名字,在「SaveAs」裏面輸入「02-HelloWorld」,而後選擇「Save」。如圖2-2所示
圖2-2,輸入項目的名字
第四步,獲得一個如圖2-3所示的一個畫面。嘗試一下用鼠標分別點擊左側窗口欄裏面的「02-HelloWorld」,「Source」.「Documentation」,「ExternalFrameworksandLibraries」,「Products」,而後觀察一下右邊的窗口都出現了什麼東西。通常來講,「02-HelloWorld」就是項目的名字下面是項目全部的文件的列表。項目下面的子目錄分別是和這個項目相關的一些虛擬或者實際上的目錄。爲何我說是虛擬的呢?你們能夠經過Finder打開你的工程文件的目錄,你會發現你的全部文件竟然都在根目錄下,根本就不存在什麼「Source」之類的目錄。
圖2-3,項目瀏覽窗口
第五步,選擇屏幕上方菜單的「Run」而後選擇「Console」,出現瞭如圖2-4所示的畫面,用鼠標點擊窗口中間的「BuildandGo」按鈕。
圖2-4,運行結果畫面
若是不出什麼意外的話,你們應該看到咱們熟悉得不能再熟悉的「HelloWolrd!」。因爲咱們沒有寫任何的代碼,因此從理論上來講,這部分代碼不該該出現編譯錯誤。好的,從下面開始,筆者要開始對這個HelloWorld裏面的一些新鮮的東西進行講解。
2.2,頭文件導入
在Java或者C/C++裏面,當咱們的程序須要引用外部的類或者方法的時候,須要在程序源文件中包含外部的類以及方法的包(java裏面的jarpackage)或者頭文件(C/C++的.h),在Objective-C裏面也有相相似的機制。筆者在這一節裏面將要向你們介紹在Objective-C裏面,頭文件是怎樣被包含進來的。
請同窗們到Xcode開發環境的左側窗口裏面,點擊Source文件夾,而後就在右側部分看到了代碼源文件的列表,找到02-HelloWorld.m以後單擊會在Xcode的窗口裏面出現,雙擊鼠標代碼會在一個新窗口出現,請同窗們按照這種方法打開"02-HelloWorld.m"。
對於Java程序來講,源程序的後綴爲.java,對於C/C++代碼來講,後綴爲c/cpp,如今咱們遇到了.m。當Xcode看到了.m文件以後,就會把這個文件看成Objective-C文件來編譯。同窗們也許會猜到,當Xcode遇到c/cpp,或者java的時候也會對應到相應的語言的。
好的,咱們順便提了一下Xcode對.m文件的約定,如今咱們開始從第一行代碼講起,請參看下列代碼:
1#import<Foundation/Foundation.h>
2
3intmain(intargc,constchar*argv[]){
4NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
5
6//insertcodehere
7NSLog(@"Hello,World!");
8[pooldrain];
9return0;
10}
11
有過C/C++經驗的同窗看到第一行,也許會以爲有些親切;有過Java經驗的同窗看到第一行也許也會有一種似曾相識的感受。同窗們也許猜到了這是幹什麼用的,沒錯,這個正是頭文件。不過,在C/C++裏面是#include,在java裏面是import,這裏是#import。
在C/C++裏面會有#include互相包含的問題,這個時候須要#ifdef來進行編譯的導向,在Xcode裏面,同窗們能夠"放心的"包含各類東西,這個沒有關係,由於咱們的編譯器有足夠的「聰明」,由於同一個頭文件只是被導入一次。除了#import變得聰明瞭一點以外,和#include的功能是徹底同樣的。
咱們再來看看咱們的另一個新的朋友---Foundation.h。這個是系統框架Foundationframework的頭文件,有了它你能夠免費的獲取系統或者說蘋果公司爲你精心準備的一系列方便你使用的系統功能,好比說字符串操做等等。Foundation框架從屬於Cocoa框架集,Cocoa的另一個框架爲ApplicationKit,或者是UIKit,其中前者的應用對象爲MACOS,後者的應用對象爲iPhoneOS。本系列入門指南將只是使用Foundation,由於筆者須要向同窗們介紹Objective-C的基本使用方法,爲了不過多的新鮮東西給同窗們形成閱讀上的困難,因此命令行就已經足夠了。
說到這裏,筆者須要澄清一點,其實MACOS的Cocoa和iPhone的Cocoa是不同的,能夠說,其中iPhone是MACOS的一個子集。
2.3,main函數
有過C/C++或者java經驗的同窗們對第3行代碼應該很熟悉了,是的你們都同樣主程序的入口都是main。這個main和C/C++語言裏面的main是徹底同樣的,和java語言在本質上也是徹底同樣的。由於Objective-C徹底的繼承了C語言的特性。確切的說,不是說Objective-C和C語言很類似,而是Objective-C和C語言是徹底兼容的。
關於main函數是幹什麼用的,筆者就不在這裏羅嗦了,有興趣的同窗能夠找一本C語言的書看看。
2.4,關於NSAutoreleasePool
本身動手,豐衣足食---
在第4行,咱們遇到了另一個新鮮的東西,這就是NSAutoreleasePool。
讓我把這個單詞分爲三部分,NS,Autorelease和Pool。
當咱們看到NS的時候,也許不知道是表明着什麼東西。NS其實只是一個前綴,爲了不命名上的衝突。NS來自於NeXTStep的一個軟件,NeXTSoftware的縮寫,NeXTSoftware是Cocoa的前身,一開始使用的是NS,爲了保持兼容性因此NS一直得以保留。在多人開發的時候,爲了不命名上的衝突,開發組的成員最好事先定義好各自的前綴。可是,最好不要有同窗使用NS前綴,這樣會讓其餘人產生誤解。
略微有些遺憾的是,Objective-C不支持namespace關鍵字,不知道後續的版本是否會支持。
下面咱們討論一下Autorelease和Pool。
程序在執行的時候,須要向系統申請內存空間的,當內存空間再也不被使用的時候,毫無疑問內存須要被釋放,不然有限的內存空間會很快被佔用光光,後面的程序將沒法獲得執行的有效內存空間。從計算機技術誕生以來,無數的程序員,咱們的無數先輩都在爲管理內存進行努力的工做,發展到如今,管理內存的工做已經獲得了很是大的完善。
在Objective-C或者說Cocoa裏面,有三種內存的管理方式。
第一種,叫作「GarbageCollection」。這種方式和java相似,在你的程序的執行過程當中,始終有一個高人在背後準確地幫你收拾垃圾,你不用考慮它何時開始工做,怎樣工做。你只須要明白,我申請了一段內存空間,當我再也不使用從而這段內存成爲垃圾的時候,我就完全的把它忘記掉,反正那個高人會幫我收拾垃圾。遺憾的是,那個高人須要消耗必定的資源,在攜帶設備裏面,資源是緊俏商品因此iPhone不支持這個功能。因此「GarbageCollection」不是本入門指南的範圍,對「GarbageCollection」內部機制感興趣的同窗能夠參考一些其餘的資料,不過說老實話「GarbageCollection」不大適合適初學者研究。
第二種,叫作「ReferenceCounted」。就是說,從一段內存被申請以後,就存在一個變量用於保存這段內存被使用的次數,咱們暫時把它稱爲計數器,當計數器變爲0的時候,那麼就是釋放這段內存的時候。好比說,當在程序A裏面一段內存被成功申請完成以後,那麼這個計數器就從0變成1(咱們把這個過程叫作alloc),而後程序B也須要使用這個內存,那麼計數器就從1變成了2(咱們把這個過程叫作retain)。緊接着程序A再也不須要這段內存了,那麼程序A就把這個計數器減1(咱們把這個過程叫作release);程序B也再也不須要這段內存的時候,那麼也把計數器減1(這個過程仍是release)。當系統(也就是Foundation)發現這個計數器變成了0,那麼就會調用內存回收程序把這段內存回收(咱們把這個過程叫作dealloc)。順便提一句,若是沒有Foundation,那麼維護計數器,釋放內存等等工做須要你手工來完成。
這樣作,有一個明顯的好處就是,當咱們不知道是A先不使用這段內存,仍是B先不使用這段內存的時候,咱們也能夠很是簡單的控制內存。不然,當咱們在程序A裏面釋放內存的時候,還須要看看程序B是否還在使用這段內存,不然咱們在程序A裏面釋放了內存以後,可憐的程序B將沒法使用這段內存了。這種方式,尤爲是在多線程的程序裏面很重要,若是多個線程同時使用某一段內存的時候,安全的控制這些內存成爲數日才的程序員的夢魘。
若是有同窗搞過COM的話,那麼應該對Release/AddRef很熟悉了,其實Obejctive-C和他們的機制是同樣的。
接下來,我須要解釋一下Autorelease方式。上述的alloc->retain->release->dealloc過程看起來比較使人滿意,可是有的時候不是很方便,咱們代碼看起來會比較羅嗦,這個時候就須要Autorelease。Autorelease的意思是,不是當即把計數器減1而是把這個過程放在線程裏面加以維護。當線程開始的時候,須要通知線程(NSAutoreleasePool),線程結束以後,才把這段內存釋放(drain)。Cocoa把這個維護全部申請的內存的計數器的集合叫作pool,當再也不須要pool(水池)的時候就要drain(放水)。
筆者想要說的是,雖然iPhone支持Autorelease可是咱們最好不要使用。由於Autorelease方式從本質上來講是一種延遲釋放內存的機制,手機的空間容量有限,咱們必須節約內存,肯定不須要的內存應該趕快釋放掉,不然當你的程序使用不少內存的狀況下也許會發生溢出。這一個習慣最好從剛剛開始學習使用Objective-C的時候就養成,不然長時間使用Autorelease會讓你變得「懶散」,萬一遇到問題的時候,解決起來會很是耗費時間的。因此,仍是關於內存管理,咱們仍是本身動手,豐衣足食。固然筆者不是說絕對不能夠使用,而是當使用Autorelease能夠明顯減低程序複雜度和易讀性的時候,仍是考慮使用一下換一下口味。
說到這裏,可能有的同窗已經開始發暈了,認爲這個東西比較難以理解。是的,筆者在這裏只是介紹一個大概的東西,在這裏只要瞭解計數器的概念就能夠了,筆者將在隨後的章節裏面對這個功能加以詳細論述,請同窗們放心,這個東西和HelloWorld同樣簡單。
關於Pool
在使用Pool的時候,也要記住系統給你的Pool的容量不是無限大的,從這一點來講和在現實世界的信用卡比較類似。
你能夠在必定程度透支,可是若是「忘記掉」信用卡的額度的話,會形成很大的系統風險。
第三種,就是傳統而又原始的C語言的方式,筆者就不在這裏敘述了。除非你在Objective-C裏面使用C代碼,不然不要使用C的方式來申請和釋放內存,這樣會增長程序的複雜度。
線程是什麼東西?線程指的是進程中一個單一順序的控制流。它是系統獨立調度和分派的基本單位。同一進程中的多個線程將共享該進程中的所有系統資源,好比文件描述符和信號處理等等。一個進程能夠有不少線程,每一個線程並行執行不一樣的任務。
2.5,關於[[NSAutoreleasePoolalloc]init];
關於程序第4行等號右邊出現的括弧以及括弧裏面的內容,筆者將在後續的章節裏面介紹。在這裏,同窗們能夠理解爲,經過告訴Objective-C編譯器[[NSAutoreleasePoolalloc]init],編譯器就會成功的編譯生成NSAutoreleasePoo對象的代碼就能夠了。
2.6,Objective-C裏面的註釋
同窗們在第6行看到了//的註釋,這個和C++以及Java是同樣的,表示這一行的內容是註釋,編譯器將會忽略這一行的內容。筆者在上面說過Objective-C徹底兼容C語言,因此C語言裏面傳統的/**/在Objective-C裏面也是有效的。
2.7,命令行輸出
第7行,咱們看到了NSLog這個函數。NS上面已經講過了,咱們都知道Log是什麼意思,那麼這段代碼的意思就是輸出一個字符串,Xcode的代碼生成器本身把字符串定義爲「Hello,World!」。NSLog至關於C語言裏面的printf,因爲咱們是在使用Objective-C因此筆者將會和同窗們一塊兒,在這裏暫時忘記掉咱們過去曾經熟悉的printf。
有眼光銳利的同窗會發如今字符串的前面多了一個@符號,這是一個什麼東西呢?
如前所述,Objective-C和C是徹底兼容的,可是NSLog這個函數須要的參數是NSString,這樣就產生了一個問題,若是使用C的字符串方式的話,爲了保持和C的兼容性編譯器將會把字符串理解爲C的字符串。爲了和C的字符串劃清界限,在C的字符串前面加上@符號,Objective-C的編譯器會認爲這是一個NSString,是一個NSLog喜歡的參數。
爲何NSLog或者Cocoa喜歡使用NSString?由於NSString封裝了一系列的字符串的方法好比字符串比較,字符串和數字相互轉換等等的方法,使用起來要比C的字符串方便的多。
2.8,本章總結
很是感謝同窗們耐心的看到這裏!
經過理解本章的內容,同窗們應該能夠使用Xcode建立一個命令行的工程,理解.m文件的基本要素,理解內存的管理方法的思路,還有Objective-C的註釋的寫法,以及命令行的輸出方法。
是否是很簡單又頗有樂趣呢?筆者將會盡最大努力把看起來複雜的東西講解的簡單一些,而且真心的但願你們能夠從中找到樂趣。
3,類的聲明和定義
本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
上一章咱們寫了一個很是簡單的Obejctive-C下面的Hello,World!的小程序,而且對裏面出現的一些新的概念進行了解釋。這一章,咱們將要深刻到Objective-C的一個基本的要素,也就是類的聲明和定義。經過本章的學習,同窗們應該能夠定義類,給類加上變量,還有經過方法訪問類的變量。不過準確的說,變量和方法的名詞在Objective-C裏面並非最準確的稱呼,咱們暫時引用Java的定義,稍後咱們將統一咱們的用語定義。
3.1,本章的程序的執行結果。
咱們將構築一個類,類的名字叫作Cattle,也就是牛的意思,今年是牛年並且我還想給在股市奮戰的同窗們一個好的名字,因此咱們暫時把這個類叫作牛類。
咱們在main裏面初始化這個牛類,而後調用這個類的方法設定類的變量,最後調用這個類的一個方法,在屏幕上輸出,最終輸出的結果以下圖3-1所示
圖3-1,牛類的輸出結果
完整的代碼在這裏。不過爲了熟悉編輯環境以及代碼,筆者強烈建議同窗們按照下面的步驟本身輸入。
3.2,實現步驟
第一步,按照咱們在第二章所述的方法,新建一個項目,項目的名字叫作03-HelloClass。固然,你也能夠起一個別的更好聽的名字,好比說HelloCattle等等,這個並不妨礙咱們的講解。若是你是第一次看本系列文章,請到這裏參看第二章的內容。
第二步,把鼠標移動到左側的窗口的「Source」目錄,而後單擊鼠標右鍵,選擇「Add」,而後界面上會出來一個子菜單,在子菜單裏面選擇「NewFile...」。如圖3-2所示:
圖3-2,新建文件
第三步,在新建文件對話框的左側選擇「CocoaTouchClasses」,而後在右側窗口選擇「NSObjectsubclass」,而後單擊「Next」。如圖3-3所示:
第四步,在「NewFile」對話框裏面的「FileName」欄內輸入「Cattle.m」。注意,在確省狀態下,Xcode爲你加上了「.m」的後綴,這個也是編譯器識別Objective-C源文件的方法,沒有特殊理由請不要修改這個後綴,不然會讓編譯器感到不舒服。另外請確認文件名字輸入欄的下方有一個「Alsocreate"Cattel.h"」選擇框,請保持這個選擇框爲選擇的狀態。如圖3-4所示。
第5步,在項目瀏覽器裏面選擇「Cattle.h」文件,把文件改成以下代碼而且保存(Command鍵+S):
#import<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
}
-(void)saySomething;
-(void)setLegsCount:(int)count;
@end
爲何legsCattle者,牛也;legs者,股也。不過牛股裏面的牛正確的英文說法應該是Bull,請你們不要着急,咱們會在類的繼承裏面命名一個Bull類的。
第六步,在項目瀏覽器裏面選擇「Cattle.m」文件,把文件改成以下代碼而且保存(Command鍵+S):
#import"Cattle.h"
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
@end
第七步,在項目瀏覽器裏面選擇「03-HelloClass.m」文件,把文件改成以下代碼而且保存(Command鍵+S):
#import<Foundation/Foundation.h>
#import"Cattle.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
[cattlesetLegsCount:4];
[cattlesaySomething];
[pooldrain];
return0;
}
第八步,選擇屏幕上方菜單裏面的「Run」,而後選擇「Console」,打開了Console對話框以後,選擇對話框上部中央的「BuildandGo」,若是不出什麼意外的話,那麼應該出現入圖3-1所示的結果。若是出現了什麼意外致使錯誤的話,那麼請仔細檢查一下你的代碼。若是通過仔細檢查發現仍是不能執行的話,能夠到這裏下載筆者爲同窗們準備的代碼。若是筆者的代碼仍是不能執行的話,請告知筆者。
3.3,類的聲明
從Objective-C名字咱們就能夠得知,這是一個面向對象的語言。面向對象的一個最基礎的要素就是類的概念,Objective-C也不例外。所謂的類的概念,實際上是從C語言的結構體發展而來的。咱們知道,C語言裏面的結構體僅僅有數據的概念,面向對象的語言不只僅支持數據,還能夠在結構體裏面封裝用於存取結構體數據的方法。結構體的數據和方法結合,咱們把整個結構體稱爲類(Class)。僅僅有了類,是不能執行任何操做的,咱們必須把類進行實體化,實體化後的類咱們稱之爲對象(Object)。從這個角度上來講,咱們能夠認爲類是對象的模版。
若是要使用類,那麼和構造體相相似,咱們必須聲明這個類。
請參照「Cattle.h」文件:
1#import<Foundation/Foundation.h>
2
3
4@interfaceCattle:NSObject{
5intlegsCount;
6}
7-(void)saySomething;
8-(void)setLegsCount:(int)count;
9@end
若是看過本系列第二章的同窗們,第一行應該是一個老面孔了,咱們知道咱們須要這個東西免費得到蘋果公司爲咱們精心準備的FoundationFramework裏面的不少的功能。若是不使用這個東西的話,咱們的工做將會很複雜。
同窗們請看第4行和第9行的第一個字母,又出現了「@」符號。爲何說又呢,由於咱們在第二章的字符串前面也看到過這個東西。字符串前面出現這個符號是由於咱們須要和C語言的字符串定義區別開來,咱們須要編譯器導向。在這裏,我要告訴同窗們的是,這裏的「@」符號的做用仍是一樣是編譯器導向。咱們知道Java和C++定義了一個關鍵字class用於聲明一個類,在Objective-C裏面,不存在這樣的關鍵字。在Objective-C裏面,類的定義從@interface開始到@end結束,也就是說,編譯器看到了@interface就知道了這是類的定義的開始,看到了@end就知道,類的定義結束了。
咱們這裏類的名字是「Cattle」,咱們使用了空格和@interface分開,通知編譯器,咱們要聲明一個類,名字叫作Cattle。在Cattle的後面,咱們有「:NSObject」,這是在通知編譯器咱們的Cattle是從NSObject繼承而來的,關於繼承和NSObject,咱們將在後面的章節裏面詳細介紹,關於「:NSObject」咱們如今能夠理解爲,經過這樣寫,咱們免費得到了蘋果公司爲咱們精心準備的一系列的類和對象的必備的方法。NSObject被稱爲rootclass,也就是根類。在Java或者.NET裏面,根類是必備的,C++不須要。在Obejctive-C裏面原則上,你能夠不使用NSObject,構築一個你本身的根類,可是事實上這樣作將會有很大工做量,並且這樣作沒有什麼意義,由於蘋果爲你提供的NSObject通過了很長時間的檢驗。也許有好奇心的同窗們想本身構築根類,不過至少筆者不會有本身去構築一個根類的慾望。
好的,你們如今來看第5行。咱們之前把這個東西叫作變量,咱們從如今開始,須要精確的使用Objective-C的用語了,這是實體變量(instancevariables,在有的英文資料裏面會簡寫爲iVars)。雖然做爲一個Cattle,它有不止一個實體變量,好比說體重等等,可是爲了代碼簡潔,咱們在這裏聲明一個就是牛腿也就是牛股的數目,這個實體變量是int型,表示一個整數,咱們固然不但願有4.5個牛腿。
咱們來看第6行,第6行的括弧和在第4行最後的括弧用來表示實體變量的定義區間,編譯器認爲在這兩個括弧之間的定義是實體變量的定義。固然,若是你的類沒有實體變量,那麼這兩個括弧之間容許什麼都沒有。和Java以及C++不同,Objective-C要求在括弧裏面不能有方法也就是函數的定義,那麼Objective-C裏面的方法的定義放在什麼地方呢,請看第7行。
第7行的第一個字母是一個減號「-」。這個減號就是告訴編譯器,減號後面的方法,是實體方法(instancemethod)。實體方法的意思就是說,這個方法在類沒有被實體化以前,是不能運行的。咱們在這裏看到的是減號,在有減號的同時也有加號,咱們把帶加號的方法稱爲類方法(classmethod),和實體方法相對應,類方法能夠脫離實體而運行。關於類方法,咱們將在後面的章節裏面講解。你們也許能夠想起來在C++和Java裏面一樣也有相似的區分,不是麼。
在Objective-C裏面方法的返回類型須要用圓括號包住,當編譯器看到減號或者加號後面的括號了以後,就會認爲這是在聲明方法的返回值。你也能夠不聲明返回值,Objective-C的編譯器會給沒有寫顯式的返回值函數加上一個默認的返回值,它的類型是id,關於id類型咱們將在後面講解,不過筆者不推薦不寫返回值的類型。
在第7行咱們定義了這個方法的名字是saySomething,固然Cattle說的話咱們人類是聽不懂的,筆者只是想讓它在咱們的控制檯裏面輸出一些咱們能夠看得懂得字符串。方法的聲明最後,須要分號來標識,這一點保持了和C沒有任何區別。
咱們再來看看第8行,第8行和第7行多了「:(int)count」。其中冒號放在方法的後面是用來表示後面是用來定義變量的,一樣變量的類型使用括號給包住,若是不寫變量的類型的化,編譯器一樣認爲這是一個id類型的。最後的count,就是變量的名字。若是有不僅一個變量怎麼辦?答案就是在第一個變量後面加冒號,而後加園括號包住變量的類型,接着是變量的名字。
好了,咱們在這裏總結一下,類的定義方法以下:
@interface類的名字:父類的名字{
實體變量類型實體變量名字;
}
-(返回值類型)方法名字;
+(返回值類型)方法名字;
-(返回值類型)方法名字:(變量類型)變量名字標籤1:(變量類型)變量1名字;
@end
...的意思在本系列入門講座裏面,...表示省略了一些代碼的意思。
3.4,類的定義
咱們在前一節講述了類的聲明,咱們下一步將要看一下類的定義。請同窗們打開「Cattle.m」文件:
1#import"Cattle.h"
2
3
4@implementationCattle
5-(void)saySomething
6{
7NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
8}
9-(void)setLegsCount:(int)count
10{
11legsCount=count;
12}
13@end
14
Cattle.m文件的第一行就import了Cattle.h文件,這一點和C的機制是同樣的,關於#import的說明請參照第二章。
咱們來看第4行和第13行,和頭文件裏面的@同樣,咱們這裏類的定義也是使用的編譯導向。編譯器會把從@implementation到@end之間的部分看做是類的定義。@implementation的後面有一個空格,空格的後面是咱們的類的名字Cattle,這是在告訴編譯器,咱們要定義Cattle類了。第4行和第13行之間是咱們在頭文件裏面定義的實體方法或者類方法的定義部分,固然咱們的類若是沒有任何的實體方法和類方法的話,咱們也許要寫上@implementation和@end,把中間留爲空就能夠了。
第5行是咱們定義的saySomething的實現,咱們能夠發現第5行的內容和頭文件Cattle.h的第7行是一致的。筆者我的認爲在編寫實體方法和類方法的定義的時候,爲了不手工輸入產生的偏差,能夠從頭文件當中把聲明的部分拷貝過來,而後刪除掉分號,加上兩個花括弧。咱們知道地6行到第8行是方法的定義的部分,咱們再來看看第7行。第7行和第二章的Hello,World輸出有些類似,只不過多了一個%d,還有實體變量legsCount,這個寫法和C語言裏面的printf是相似的,輸出的時候會使用legsCount來替代字符串裏面的%d。
第9行的內容和Cattle.h的第8行一致的,這個不須要再解釋了。咱們來看看第11行,第11行是在說,把參數count的數值賦值給實體變量legsCount。咱們能夠經過使用setLegsCount方法來控制Cattle對象裏面legsCount的數值。
這部份內容的關鍵點爲@implementation和@end,理解了這個東西,其他的就不難理解了。咱們來總結一下,類的定義部分的語法:
@implementation類的名字
-(方法返回值)方法名字
{
方法定義
}
-(方法返回值)方法名字:(變量類型)變量名字
{
方法定義
}
@end
3.5,類的實例化
咱們在3.3和3.4節裏面分別聲明和定義了一個Cattle的類。雖然定義好的類,可是咱們是不能直接使用這個類的。由於類的內容須要被調入到內存當中咱們稱之爲內存分配(Allocation),而後須要把實體變量進行初始化(Initialization),當這些步驟都結束了以後,咱們的類就被實例化了,咱們把實例化完成的類叫作對象(Object)。好的,咱們知道了咱們在類的實例化過程中須要作哪些工做,咱們接着來看看咱們已經搞定的Cattle類的定義和聲明是怎樣被實例化的。
1#import<Foundation/Foundation.h>
2#import"Cattle.h"
3
4intmain(intargc,constchar*argv[]){
5NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
6
7idcattle=[Cattlenew];
8[cattlesetLegsCount:4];
9[cattlesaySomething];
10
11[pooldrain];
12return0;
13}
同窗們請看第7行的第一個單詞id。id是英文identifier的縮寫,咱們在不少地方都遇到過id,好比說在博客園裏面,咱們都使用id來登錄系統的,咱們的id就表明着系統的一個用戶。因爲id在一個系統當中是惟一的,因此係統得到咱們的id以後就知道咱們是誰了。Objective-C也是同樣的道理,使用id來表明一個對象,在Objective-C當中,全部的對象均可以使用id來進行區分。咱們知道一個類僅僅是一些數據外加上操做這些數據的代碼,因此id其實是指向數據結構的一個指針而已,至關於void*。
第7行的第二個單詞是cattle,就是咱們給這個id起的一個名字。固然,你能夠起系統保留的名字之外的任何名字,不過爲了維持代碼的可讀性,咱們須要一個有意義的名字,咱們這裏使用頭文字爲小寫的cattle。
第7行的[Cattlenew]是建立對象,new其實是alloc和init的組合,在Objective-C裏面建立對象是一個爲對象分配內存和初始化的過程。new,alloc還有init定義在Cattle的超類NSObject裏面,筆者將要在第7章裏面詳細的解釋一下如何建立對象。在第7章以前咱們都是用new來建立對象。
Objective-C裏面的方法的使用和其餘語言有些不一樣,Objective-C使用消息(Message)來調用方法。因此筆者認爲在講解第7行等號右邊的部分以前,須要首先向你們介紹一個咱們的新朋友,消息(Message)。所謂的消息就是一個類或者對象能夠執行的動做。消息的格式以下:
[對象或者類名字方法名字:參數序列];
首先咱們觀察到有兩個中括弧,最右邊的括弧以後是一個分號,當編譯器遇到了這個格式以後會把中間的部分看成一個消息來發送。在上文的表達式當中,包括中括弧的全部部分的內容被稱做消息表達式(Messageexpression),「對象或者類名字」被稱做接收器(Receiver),也就是消息的接受者,「方法名字:參數序列」被稱爲一個消息(Message),「方法名字」被稱做選擇器(Selector)或者關鍵字(Keyword)。Objective-C和C語言是徹底兼容的,C語言裏面的中括弧用於表示數組,可是數組的格式明顯和消息的發送的格式是不同的,因此咱們能夠放心,編譯器不會把咱們的消息發送看成一個數組。
咱們來回憶一下C語言裏面函數的調用過程,實際上編譯器在編譯的時候就已經把函數相對於整個執行包的入口地址給肯定好了,函數的執行實際上就是直接從這個地址開始執行的。Objective-C使用的是一種間接的方式,Objective-C向對象或者類(具體上是對象仍是類的名字取決於方法是實體方法仍是類方法)發送消息,消息的格式應該和方法相同。具體來講,第7行等號右邊的部分[Cattlenew]就是說,向Cattle類發送一個new的消息。這樣當Cattle類接收到new的時候,就會查找它能夠相應的消息的列表,找到了new以後就會調用new的這個類方法,分配內存和初始化完成以後返回一個id,這樣咱們就獲得一個對象。
Objective-C在編譯的過程中,編譯器是會去檢查方法是否有效的,若是無效會給你一個警告。可是編譯器並不會阻止你執行,由於只有在執行的時候纔會觸發消息,編譯器是沒法預測到執行的時候會發生什麼奇妙的事情的。使用這樣的機制給程序毫無疑問將給帶來極大的靈活性,由於咱們和任意的對對象或者類發送消息,只要咱們能夠保證執行的時候類能夠準確地找到消息而且執行就能夠了,固然若是找不到的話,運行會出錯。
任何事物都是一分爲二的---
任何事物都是一分爲二的,在咱們獲得了靈活性的時候咱們損失的是執行的時間。Objective-C的這種方式要比直接從函數的入口地址執行的方式要消耗更多的執行時間,雖然編譯器對尋找的過程做過必定的優化。
有的同窗會以爲奇怪,咱們在Cattle裏面並無定義new,咱們能夠向Cattle發送這個類方法麼?答案是能夠,由於new在NSObject裏面,實際上響應new消息的是NSObject。實際上new相似於一個宏,並非一個「原子」的不可再分的方法,關於詳細的狀況,咱們將在後續的章節裏面講解。
有了第7行的講解,那麼第8行的內容就不難理解了,第8行其實是想cattle對象發送一個setLegsCount的消息,參數是4,參照Catttle.m,咱們能夠發現這個時候咱們但願實體變量legsCount是4。第8行就更簡單了,就是說向cattle對象發送一個saySomething的消息,從而實現了控制檯的輸出。
3.6,本章總結
經過本章的學習,同窗們應該掌握以下概念
- 如何聲明一個類
- 如何定義一個類
- 實體變量的定義
- 類方法和實體方法的定義
- id是什麼
- NSObject的奇妙做用
- 如何從類開始初始化對象
- 消息的調用
4,繼承
本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
上一章筆者介紹了一下在Objective-C裏面的類的基本構造和定義以及聲明的方法。咱們知道在面向對象的程序裏面,有一個很重要的需求就是代碼的重複使用,代碼的重複使用的重要方法之一就是繼承。咱們在這一章裏面,將要仔細的分析一下繼承的概念以及使用的方法。有過其餘面嚮對象語言的同窗,對這一章的內容應該不會感到陌生。
4.1,本章的程序的執行結果
在本章裏面,咱們將要重複使用第3章的部分代碼。咱們在第3章構築了一個叫作Cattle的類,咱們在這一章裏面須要使用Cattle類,而後基於Cattle類,咱們須要構築一個子類,叫作Bull類。Bull類裏面,咱們追加了一個實例變量,名字叫作skinColor,咱們也將要追加2個實例方法,分別getSkinColor還有setSkinColor。咱們而後須要更改一下咱們的main函數,而後在main函數裏面讓咱們的Bull作一下重要講話。第4章程序的執行結果如圖4-1所示:
圖4-1,本章程序的執行結果
4.2,實現步驟
第一步,按照咱們在第二章所述的方法,新建一個項目,項目的名字叫作04-HelloInheritance。若是你是第一次看本篇文章,請到這裏參看第二章的內容。
第二步,把鼠標移動到項目瀏覽器上面的「Source」上面,而後在彈出的菜單上面選擇「Add」,而後在子菜單裏面選擇「ExsitingFiles」,如圖4-2所示
圖4-2,向項目追加文件
第三步,在文件選擇菜單裏面,選擇第3章的項目文件夾「03-HelloClass」,打開這個文件夾以後,用鼠標和蘋果電腦的COMMAND鍵,選澤文件「Cattle.h」和「Cattle.m」,而後按下「Add」按鈕,如圖4-3所示。若是你沒有下載第3章的代碼,請點擊這裏下載。
圖4-3,選擇文件
第四步,在追加文件的選項對話框裏面,讓「Copyitemsintodestinationgroup'sfolder(ifneeded)」的單選框變爲被選擇的狀態。這樣就保證了咱們在第三步裏面選擇的文件被拷貝到了本章的項目裏面,能夠避免咱們不當心更改「Cattle.h」和「Cattle.m」對已經生效的第3章程序產生影響,雖然咱們在本章裏面不更改這2個代碼。
第五步,把鼠標移動到項目瀏覽器上面的「Source」上面,而後在彈出的菜單上面選擇「Add」,而後在子菜單裏面選擇「NewFiles」,而後在新建文件對話框的左側選擇「CocoaTouchClasses」,而後在右側窗口選擇「NSObjectsubclass」,選擇「Next」,在「NewFile」對話框裏面的「FileName」欄內輸入「Bull.m」。在這裏筆者沒有給出圖例,在這裏新建文件的步驟和第3章的第二步到第四步相同,只是文件名字不同。第一次看到本篇文章的同窗能夠參照第3章。
第六步,打開Bull.h作出以下修改,而且保存。
#import<Foundation/Foundation.h>
#import"Cattle.h"
@interfaceBull:Cattle{
NSString*skinColor;
}
-(void)saySomething;
-(NSString*)getSkinColor;
-(void)setSkinColor:(NSString*)color;
@end
第七步,打開Bull.m作出以下修改,而且保存
#import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
@end
第八步,打開04-HelloInheritance.m文件,作出以下修改,而且保存
#import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
[cattlesetLegsCount:4];
[cattlesaySomething];
idredBull=[Bullnew];
[redBullsetLegsCount:4];
[redBullsetSkinColor:@"red"];
[redBullsaySomething];
Bull*blackBull=[Bullnew];
[blackBullsetLegsCount:4];
[blackBullsetSkinColor:@"black"];
[blackBullsaySomething];
[pooldrain];
return0;
}
第九步,選擇屏幕上方菜單裏面的「Run」,而後選擇「Console」,打開了Console對話框以後,選擇對話框上部中央的「BuildandGo」,若是不出什麼意外的話,那麼應該出現入圖4-1所示的結果。若是出現了什麼意外致使錯誤的話,那麼請仔細檢查一下你的代碼。若是通過仔細檢查發現仍是不能執行的話,能夠到這裏下載筆者爲同窗們準備的代碼。若是筆者的代碼仍是不能執行的話,請告知筆者。
4.3,子類Subclass和超類Superclass
讓咱們首先回憶一下第3章的Cattle.h,在Cattle.h裏面咱們有以下的代碼片段:
@interfaceCattle:NSObject{
這段代碼是在告訴編譯器,咱們的Cattle是繼承的NSObject。在這段代碼當中,NSObject是超類,Cattle是子類。經過這樣寫,咱們曾經免費的獲得了NSObject裏面的一個方法叫作new。
idcattle=[Cattlenew];
在面向對象的程序設計當中,若是在子類當中繼承了超類的話,那麼超類當中已經生效的部分代碼在子類當中仍然是有效的,這樣就大大的提升了代碼的效率。基於超類咱們能夠把咱們須要追加的一些功能放到子類裏面去,在本章裏面,咱們決定基於Cattle類,從新生成一個子類Bull:
1#import<Foundation/Foundation.h>
2#import"Cattle.h"
3
4@interfaceBull:Cattle{
5NSString*skinColor;
6}
7-(void)saySomething;
8-(NSString*)getSkinColor;
9-(void)setSkinColor:(NSString*)color;
10@end
上段代碼裏面的第2行,是通知編譯器,咱們這個類的聲明部分須要Cattle.h文件。這個文件咱們已經很熟悉了,是咱們在第3章曾經構築過的,在本章裏面,咱們不會改變裏面的任何內容。
第4行,就是在通知編譯器,咱們須要聲明一個類名字叫作Bull,從Cattle裏面繼承過來。
第5行,咱們追加了一個實例變量skinColor,用來保存Bull的顏色。
第7行,咱們重載了在Cattle類裏面已經有的(void)saySomething實例方法。重載(void)saySomething方法的主要緣由是,咱們認爲Bull說的話應該和Cattle有所區別。
第8行到第9行,咱們爲Bull類聲明瞭兩個新的方法(NSString*)getSkinColor和(void)setSkinColor:(NSString*)color,分別用來設定和讀取咱們的實例變量skinColor。
好的,咱們總結一下繼承的時候的子類的格式。
@interface類的名字:父類的名字{
實體變量類型實體變量名字;
}
-(返回值類型)重載的方法名字;
+(返回值類型)重載的方法名字;
-(返回值類型)其餘的方法名字:(變量類型)變量名字:(變量類型)變量名字;
@end
4.4,self和super
咱們再來打開「Bull.m」,在saySomething的定義的部分,咱們發現了以下的代碼:
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
咱們在這句話當中,發現的第一個新朋友是%@,這是在告訴編譯器,須要把%@用一個後面定義的字符串來替換,在這裏咱們給編譯器提供的字符串是[selfgetSkinColor]。看到這裏,同窗們又會發現一個新的朋友self。
在類的方法定義域以內,咱們有的時候須要訪問這個類本身的實例變量,或者是方法。在類被實例化以後,咱們就能夠使用一個指向這個類自己的一個指針,在Java或者C++裏面的名字叫作this,在Objective-C裏面,這個名字是self。self自己是一個id類型的一個指針變量。咱們在第3章裏面講解過,方法的調用格式以下:
[對象或者類名字方法名字:參數序列];
在類的方法定義域裏面,當咱們須要調用類的其餘方法的時候,咱們須要指定對象或者類的名字,咱們的方法是一個實例方法因此咱們須要一個指向本身的對象,在這裏咱們須要使用self。
咱們假設,若是方法聲明裏面的參數序列裏面有一個參數的名字和類的實例變量發生重複的狀況下而且因爲某種緣由咱們沒法更改參數和實體變量的名字的話,咱們應該如何應對呢?答案是使用self,格式以下
self->變量名字
經過這樣寫,咱們能夠取獲得類的變量的數值。固然若是沒有名字衝突的話,咱們徹底能夠省略self->,Xcode也足夠的聰明可以識別咱們的實例變量,而且把咱們代碼裏面的實例變量更改成相應的醒目的顏色。
若是咱們在類的方法裏面須要訪問超類的方法或者變量(固然是訪問對子類來講是可視的方法或者變量),咱們須要怎樣寫呢?答案是使用super,super在本質上也是id的指針,因此,使用super訪問變量和方法的時候的書寫格式,和self是徹底同樣的。
「Bull.m」裏面的其餘的代碼,沒有什麼新鮮的東西,因此筆者就不在這裏贅述了。
4.5,超類方法和子類方法的執行
咱們來看一下04-HelloInheritance.m的下面的代碼片段
1idredBull=[Bullnew];
2[redBullsetLegsCount:4];
3[redBullsetSkinColor:@"red"];
4[redBullsaySomething];
5
6Bull*blackBull=[Bullnew];
7[blackBullsetLegsCount:4];
8[blackBullsetSkinColor:@"black"];
9[blackBullsaySomething];
第1行的代碼在第3章裏面講解過,咱們來看看第2行的代碼。
第2行的代碼其實是向redBull發送一個setLegsCount消息,參數爲4。咱們沒有在Bull裏面定義setLegsCount方法,可是從控制檯的輸出上來看,setLegsCount明顯是獲得了執行。在執行的時候,咱們給redBull發送setLegsCount消息的時候,runtime會在Bull的映射表當中尋找setLegsCount,因爲咱們沒有定義因此runtime找不到的。runtime沒有找到指定的方法的話,會接着須要Bull的超類,也就是Cattle。值得慶幸的是,runtime在Cattle裏面找到了setLegsCount,因此就被執行了。因爲runtime已經尋找到了目標的方法而且已經執行了,因此它就中止了尋找。咱們假設runtime在Cattle裏面也沒有找到,那麼它會接着在Cattle的超類NSObject裏面尋找,若是仍是找不到的話,因爲NSOBject是根類,因此它會報錯的。關於具體內部是一個怎樣的機制,咱們將在後面的章節裏面講解。
第3行的代碼,是設定skinColor。
第4行的代碼是給redBull發送saySomething的消息。按照第2行的runtime的尋找邏輯,它首先會在Bull類裏面尋找saySomething,這一次runtime很幸運,它一次就找到了,因此就當即執行。同時runtime也中止了尋找的過程,因此,Cattle的saySomething不會獲得執行的。
在第6行裏面,咱們定義了一個blackBull,可是這一次咱們沒有使用id做爲blackBull的類型,咱們使用了Bull*。從本質上來講,使用id仍是Bull*是沒有任何區別的。可是,咱們來想象,當咱們的程序存在不少id類型的變量的話,咱們也許就難以區分到底是什麼類型的變量了。因此,在沒有特殊的理由的狀況之下,咱們最好仍是顯式的寫清楚類的名字,這樣能夠方便其餘人閱讀。因爲Bull從Cattle繼承而來,咱們也能夠把地6行代碼改成
Cattle*blackBull=[Bullnew];
4.6,本章總結
感謝你們閱讀到這裏!咱們在本章學習了:
- 超類,子類的概念以及如何定義和聲明。
- self和super的使用方法以及使用的時機。
- 超類和子類的方法的執行。
5,Class類型,選擇器Selector以及指針函數
- 本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
- 上一章筆者介紹了在Objective-C裏面繼承的概念。有了繼承的知識咱們能夠重複的使用不少之前生效的代碼,這樣就大大的提升了代碼開發的效率。在本章,筆者要向同窗們介紹幾個很是重要的概念,Class類型,選擇器Selector以及指針函數。
- 咱們在實際上的編程過程當中,也許會遇到這樣的場景,那就是咱們在寫程序的時候不能確切的知道咱們須要使用什麼類,使用這個類的什麼方法。在這個時候,咱們須要在咱們的程序裏面動態的根據用戶的輸入來建立咱們在寫程序不知道的類的對象,而且調用這個對象的實例方法。Objective-C爲咱們提供了Class類型,選擇器Selector以及指針函數來實現這樣的需求,從而大大的提升了咱們程序的動態性能。
- 在Objective-C裏面,一個類被正確的編譯事後,在這個編譯成功的類裏面,存在一個變量用於保存這個類的信息。咱們能夠經過一個普通的字符串取得這個Class,也能夠經過咱們生成的對象取得這個Class。Class被成功取得以後,咱們能夠把這個Class看成一個已經定義好的類來使用它。
- Selector和Class比較相似,不一樣的地方是Selector用於表示方法。在Objective-C的程序進行編譯的時候,會根據方法的名字(包括參數列表)肯定一個惟一的身份證實(實際上就是一個整數),不用的類裏面的相同名字相同聲明的方法的身份證實是同樣的。這樣在程序執行的時候,runtime就不用費力的進行方法的名字比較來肯定是執行哪個方法了,只是經過一個整數的尋找就能夠立刻定位到相應的方法,而後找到相應的方法的入口地址,這樣方法就能夠被執行了。
- 筆者在前面的章節裏面敘述過,在Objective-C裏面消息也就是方法的執行比C語言的直接找到函數入口地址執行的方式,從效率上來說是比較低下的。儘管Objective-C使用了Selector等招數來提升尋找效率,可是不管如何尋找的過程,都是要消耗必定的時間的。好在Objective-C是徹底兼容C的,它也有指針函數的概念。當咱們須要執行效率的時候,好比說在一個很大的循環當中須要執行某個功能的時候,咱們能夠放棄向對某一個對象發送消息的手段,用指針函數取而代之,這樣就能夠得到和C語言同樣的執行效率了。
- 說到這裏,可能有的同窗已經有些茫然了。這些概念有些使人難以理解,可是它們確實是Objective-C的核心的功能。掌握了這些核心的功能以後,同窗們能夠很輕鬆的看懂蘋果的SDK裏面的不少東西含義,甚至能夠本身動手寫一些蘋果沒有爲咱們提供的功能。因此建議你們仔細研讀本章的內容,若是有什麼問題,能夠發個帖子你們能夠共同探討。
- 從筆者的觀點上來看,對於有Java或者C++或者其餘面向對象的語言的經驗的同窗來講,前面的從第1到第4章的內容也許有些平淡無奇。從第5章開始,咱們將要逐漸的深刻到Objective-C的核心部分。筆者的最終目的,雖然是向你們介紹iPhone開發的入門,可是筆者認爲了解了Objective-C的基本概念以及使用方法以後,熟悉iPhone的應用程序的開發將是一件水到渠成的輕鬆的事情。不然若是你直接就深刻到iPhone的開發的話,在絕大多數時間你也許由於一個小小的問題就會困擾你幾個小時甚至幾天,解決這些問題的惟一方法就是熟悉Objective-C和CocoaFoundation的特性。
- 好了,說了不少咱們從下面就要開始,咱們的手法和前面幾章是同樣的,咱們首先要介紹一下本章程序的執行結果。
5.1,本章程序的執行結果
- 圖5-1,第5章程序的執行結果
- 在本章裏面,咱們將要繼續使用咱們在前面幾章已經構築好的類Cattle和Bull。爲了靈活的使用Cattle和Bull,咱們將要構築一個新的類,DoProxy。在DoProxy裏面,咱們將會引入幾個咱們的新朋友,他們分別是BOOL,SEL,IMP,CLASS。經過這些新的朋友咱們能夠動態的經過設定文件取得Cattle和Bull的類,還有方法以及方法指針。下面將要介紹如何構築本章程序。同窗們能夠按照本章所述的步驟來構築,也能夠經過從這裏下載。不過爲了熟悉代碼的寫做,筆者強烈建議你們按照筆者所述的步驟來操做。
5.2,實現步驟
- 第一步,按照咱們在第2章所述的方法,新建一個項目,項目的名字叫作05-HelloSelector。若是你是第一次看本篇文章,請到這裏參看第二章的內容。
- 第二步,按照咱們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的「Cattle.h」,「Cattle.m」,「Bull.h」還有「Bull.m」導入本章的項目裏面。若是你沒有第4章的代碼,請到這裏下載。若是你沒有閱讀第4章的內容,請參看這裏。
- 第三步,把鼠標移動到項目瀏覽器上面的「Source」上面,而後在彈出的菜單上面選擇「Add」,而後在子菜單裏面選擇「NewFiles」,而後在新建文件對話框的左側選擇「CocoaTouchClasses」,而後在右側窗口選擇「NSObjectsubclass」,選擇「Next」,在「NewFile」對話框裏面的「FileName」欄內輸入「DoProxy.m」。在這裏筆者沒有給出圖例,在這裏新建文件的步驟和第3章的第二步到第四步相同,只是文件名字不同。第一次看到本篇文章的同窗能夠參照第3章。
- 第四步,打開「DoProxy.h」作出以下修改而且保存
- #import<Foundation/Foundation.h>
#defineSET_SKIN_COLOR@"setSkinColor:"
#defineBULL_CLASS@"Bull"
#defineCATTLE_CLASS@"Cattle"
@interfaceDoProxy:NSObject{
BOOLnotFirstRun;
idcattle[3];
SELsay;
SELskin;
void(*setSkinColor_Func)(id,SEL,NSString*);
IMPsay_Func;
ClassbullClass;
}
-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color;
-(void)setAllIVars;
-(void)SELFuncs;
-(void)functionPointers;
@end
- 第五步,打開「DoProxy.m」作出以下修改而且保存
- #import"DoProxy.h"
#import"Cattle.h"
#import"Bull.h"
@implementationDoProxy
-(void)setAllIVars
{
cattle[0]=[Cattlenew];
bullClass=NSClassFromString(BULL_CLASS);
cattle[1]=[bullClassnew];
cattle[2]=[bullClassnew];
say=@selector(saySomething);
skin=NSSelectorFromString(SET_SKIN_COLOR);
}
-(void)SELFuncs
{
[selfdoWithCattleId:cattle[0]colorParam:@"brown"];
[selfdoWithCattleId:cattle[1]colorParam:@"red"];
[selfdoWithCattleId:cattle[2]colorParam:@"black"];
[selfdoWithCattleId:selfcolorParam:@"haha"];
}
-(void)functionPointers
{
setSkinColor_Func=(void(*)(id,SEL,NSString*))[cattle[1]methodForSelector:skin];
//IMPsetSkinColor_Func=[cattle[1]methodForSelector:skin];
say_Func=[cattle[1]methodForSelector:say];
setSkinColor_Func(cattle[1],skin,@"verbose");
NSLog(@"Runningasafunctionpointerwillbemoreefficiency!");
say_Func(cattle[1],say);
}
-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color
{
if(notFirstRun==NO)
{
NSString*myName=NSStringFromSelector(_cmd);
NSLog(@"Runninginthemethodof%@",myName);
notFirstRun=YES;
}
NSString*cattleParamClassName=[aCattleclassName];
if([cattleParamClassNameisEqualToString:BULL_CLASS]||
[cattleParamClassNameisEqualToString:CATTLE_CLASS])
{
[aCattlesetLegsCount:4];
if([aCattlerespondsToSelector:skin])
{
[aCattleperformSelector:skinwithObject:color];
}
else
{
NSLog(@"Hi,Iama%@,havenotsetSkinColor!",cattleParamClassName);
}
[aCattleperformSelector:say];
}
else
{
NSString*yourClassName=[aCattleclassName];
NSLog(@"Hi,youarea%@,butIlikecattleorbull!",yourClassName);
}
}
@end
- 第六步,打開「05-HelloSelector.m」做出以下修改而且保存
- #import<Foundation/Foundation.h>
#import"DoProxy.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
DoProxy*doProxy=[DoProxynew];
[doProxysetAllIVars];
[doProxySELFuncs];
[doProxyfunctionPointers];
[pooldrain];
return0;
}
- 第七步,選擇屏幕上方菜單裏面的「Run」,而後選擇「Console」,打開了Console對話框以後,選擇對話框上部中央的「BuildandGo」,若是不出什麼意外的話,那麼應該出現入圖5-1所示的結果。若是出現了什麼意外致使錯誤的話,那麼請仔細檢查一下你的代碼。若是通過仔細檢查發現仍是不能執行的話,能夠到這裏下載筆者爲同窗們準備的代碼。若是筆者的代碼仍是不能執行的話,請告知筆者。
5.3,BOOL類型
- 咱們如今打開「DoProxy.h」文件。「DoProxy.h」文件的第3行到第5行是三個預約義的三個字符串的宏。咱們將在程序當中使用這3個宏,爲了實現代碼的獨立性,在實際的程序開發當中,咱們也許考慮使用一個配置的文本文件或者一個XML來替代這些宏。可是如今因爲筆者的主要目的是講解Objective-C的概念,爲了不較多的代碼給你們帶來理解主題的困難,因此筆者沒有使用配置文件或者XML來表述這些能夠設定的常量。
- 「DoProxy.h」的第7行對同窗們來講也是老朋友了,是通知編譯器,咱們須要聲明一個DoProxy類,從NSObject繼承。
- 咱們在第8行遇到了咱們的一個新的朋友,BOOL:
- BOOLnotFirstRun;
- 咱們定義了一個notFirstRun的實例變量,這個變量是布爾類型的。咱們的實例方法doWithCattleId須要被執行屢次,咱們在第一次執行doWithCattleId的時候須要向控制輸出包含doWithCattleId的方法名字的字符串,關於這個字符串的內容,請參考圖5-1。
- 好的,咱們如今須要看看在Objective-C裏面BOOL是怎樣定義的,咱們把鼠標移動到BOOL上面,而後單擊鼠標右鍵選擇彈出菜單的「JumptoDefinition」,而後Xcode會打開objc.h文件,咱們看到下面的代碼:
- typedefsignedcharBOOL;
//BOOLisexplicitlysignedso@encode(BOOL)=="c"ratherthan"C"
//evenif-funsigned-charisused.
#defineOBJC_BOOL_DEFINED
#defineYES(BOOL)1
#defineNO(BOOL)0
- 咱們看到這段代碼,咱們能夠這樣理解,在Objective-C裏面,BOOL實際上是signedchar,YES是1,NO是0。咱們能夠這樣給BOOL賦值:
- BOOLx=YES;
BOOLy=NO;
- 關於BOOL,實際上就是一個開關的變量,可是咱們須要注意下面2點:
- 第一點,從本質上來講BOOL是一個8bit的一個char,因此咱們在把其餘好比說short或者int轉換成爲BOOL的時候必定要注意。若是short或者int的最低的8位bit都是0的話,儘管除了最低的8位之外都不是0,那麼通過轉換以後,就變成了0也就是NO。好比說咱們有一個int的值是0X1000,通過BOOL轉換以後就變成了NO。
- 第二點,Objective-C裏面的全部的邏輯判斷例如if語句等等和C語言保持兼容,若是數值不是0判斷爲真,若是數值是0那麼就判斷爲假,並非說定義了BOOL值以後就變成了只有1或者YES爲真。因此下面的代碼的判斷都爲真:
- if(0X1000)
if(2)
if(-1)
5.4,SEL類型
- 讓咱們接着看「DoProxy.h」文件的下列代碼:
- 1idcattle[3];
2SELsay;
3SELskin;
- 其中idcattle[3]定義了一個數組用於存儲Cattle或者Bull對象。這一行代碼估計你們都很熟悉,筆者就不贅述了。像這樣的傳統的數組並不能徹底知足咱們的需求,當咱們須要作諸如追加,刪除等操做的時候,會很不方便。在隨後的章節裏面筆者將要向你們介紹傳統數組的替代解決方案NSArray。
- 上一段代碼的第二行和第三行是本節所關注的,就是SEL類型。Objective-C在編譯的時候,會根據方法的名字(包括參數序列),生成一個用來區分這個方法的惟一的一個ID,這個ID就是SEL類型的。咱們須要注意的是,只要方法的名字(包括參數序列)相同,那麼它們的ID都是相同的。就是說,無論是超類仍是子類,無論是有沒有超類和子類的關係,只要名字相同那麼ID就是同樣的。除了函數名字和ID,編譯器固然還要把方法編譯成爲機器能夠執行的代碼,這樣,在一個編譯好的類裏面,就產生了以下圖所示方法的表格示意圖(本構造屬於筆者推測,沒有獲得官方證明,因此圖5-2爲示意圖僅供參考,咱們能夠暫時認爲是這樣的)。
- 圖5-2,方法的表格示意圖
- 請注意setSkinColor後面有一個冒號,由於它是帶參數的。因爲存在這樣的一個表格,因此在程序執行的時候,咱們能夠方便的經過方法的名字,獲取到方法的ID也就是咱們所說的SEL,反之亦然。具體的使用方法以下:
- 1SEL變量名=@selector(方法名字);
2SEL變量名=NSSelectorFromString(方法名字的字符串);
3NSString*變量名=NSStringFromSelector(SEL參數);
- 其中第1行是直接在程序裏面寫上方法的名字,第2行是寫上方法名字的字符串,第3行是經過SEL變量得到方法的名字。咱們獲得了SEL變量以後,能夠經過下面的調用來給一個對象發送消息:
- [對象performSelector:SEL變量withObject:參數1withObject:參數2];
- 這樣的機制大大的增長了咱們的程序的靈活性,咱們能夠經過給一個方法傳遞SEL參數,讓這個方法動態的執行某一個方法;咱們也能夠經過配置文件指定須要執行的方法,程序讀取配置文件以後把方法的字符串翻譯成爲SEL變量而後給相應的對象發送這個消息。
- 從效率的角度上來講,執行的時候不是經過方法名字而是方法ID也就是一個整數來查找方法,因爲整數的查找和匹配比字符串要快得多,因此這樣能夠在某種程度上提升執行的效率。
5.5,函數指針
- 在講解函數指針以前,咱們先參看一下圖5-2,函數指針的數值實際上就是圖5-2裏面的地址,有人把這個地址成爲函數的入口地址。在圖5-2裏面咱們能夠經過方法名字取得方法的ID,一樣咱們也能夠經過方法ID也就是SEL取得函數指針,從而在程序裏面直接得到方法的執行地址。或者函數指針的方法有2種,第一種是傳統的C語言方式,請參看「DoProxy.h」的下列代碼片段:
- 1void(*setSkinColor_Func)(id,SEL,NSString*);
2IMPsay_Func;
- 其中第1行咱們定義了一個C語言裏面的函數指針,關於C語言裏面的函數指針的定義以及使用方法,請參考C語言的書籍和參考資料。在第一行當中,值得咱們注意的是這個函數指針的參數序列:
- 第一個參數是id類型的,就是消息的接受對象,在執行的時候這個id實際上就是self,由於咱們將要向某個對象發送消息。
- 第二個參數是SEL,也是方法的ID。有的時候在消息發送的時候,咱們須要使用用_cmd來獲取方法本身的SEL,也就是說,方法的定義體裏面,咱們能夠經過訪問_cmd獲得這個方法本身的SEL。
- 第三個參數是NSString*類型的,咱們用它來傳遞skincolor。在Objective-C的函數指針裏面,只有第一個id和第二個SEL是必需的,後面的參數有仍是沒有,若是有那麼有多少個要取決於方法的聲明。
- 如今咱們來介紹一下Objective-C裏面取得函數指針的新的定義方法,IMP。
- 上面的代碼的第一行比較複雜,使人難以理解,Objective-C爲咱們定義了一個新的數據類型就是在上面第二行代碼裏面出現的IMP。咱們把鼠標移動到IMP上,單擊右鍵以後就能夠看到IMP的定義,IMP的定義以下:
- typedefid(*IMP)(id,SEL,);
- 這個格式正好和咱們在第一行代碼裏面的函數指針的定義是同樣的。
- 咱們取得了函數指針以後,也就意味着咱們取得了執行的時候的這段方法的代碼的入口,這樣咱們就能夠像普通的C語言函數調用同樣使用這個函數指針。固然咱們能夠把函數指針做爲參數傳遞到其餘的方法,或者實例變量裏面,從而得到極大的動態性。咱們得到了動態性,可是付出的代價就是編譯器不知道咱們要執行哪個方法因此在編譯的時候不會替咱們找出錯誤,咱們只有執行的時候才知道,咱們寫的函數指針是不是正確的。因此,在使用函數指針的時候要很是準確地把握可以出現的全部可能,而且作出預防。尤爲是當你在寫一個供他人調用的接口API的時候,這一點很是重要。
5.6,Class類型
- 到目前爲止,咱們已經知道了對應於方法的SEL數據類型,和SEL一樣在Objective-C裏面咱們不只僅能夠使用對應於方法的SEL,對於類在Objective-C也爲咱們準備了相似的機制,Class類型。當一個類被正確的編譯事後,在這個編譯成功的類裏面,存在一個變量用於保存這個類的信息。咱們能夠經過一個普通的字符串取得這個Class,也能夠經過咱們生成的對象取得這個Class。Class被成功取得以後,咱們能夠把這個Class看成一個已經定義好的類來使用它。這樣的機制容許咱們在程序執行的過程中,能夠Class來獲得對象的類,也能夠在程序執行的階段動態的生成一個在編譯階段沒法肯定的一個對象。
- 由於Class裏面保存了一個類的全部信息,固然,咱們也能夠取得一個類的超類。關於Class類型,具體的使用格式以下:
- 1Class變量名=[類或者對象class];
2Class變量名=[類或者對象superclass];
3Class變量名=NSClassFromString(方法名字的字符串);
4NSString*變量名=NSStringFromClass(Class參數);
- 第一行代碼,是經過向一個類或者對象發送class消息來得到這個類或者對象的Class變量。
- 第二行代碼,是經過向一個類或者對象發送superclass消息來得到這個類或者對象的超類的Class變量。
- 第三行代碼,是經過調用NSClassFromString函數,而且把一個字符串做爲參數來取得Class變量。這個在咱們使用配置文件決定執行的時候的類的時候,NSClassFromString給咱們帶來了極大的方便。
- 第四行代碼,是NSClassFromString的反向函數NSStringFromClass,經過一個Class類型做爲變量取得一個類的名字。
- 當咱們在程序裏面經過使用上面的第一,二或者第三行代碼成功的取得一個Class類型的變量,好比說咱們把這個變量名字命名爲myClass,那麼咱們在之後的代碼種能夠把myClass看成一個咱們已經定義好的類來使用,固然咱們能夠把這個變量做爲參數傳遞到其餘的方法當中讓其餘的方法動態的生成咱們須要的對象。
5.7,DoProxy.h裏面的方法定義
- DoProxy.h裏面還有一些實例方法,關於方法的定義的格式,同窗們能夠參照第三章。咱們如今要對DoProxy.h裏面定義的方法的作一下簡要的說明。
- 1-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color;
2-(void)setAllIVars;
3-(void)SELFuncs;
4-(void)functionPointers;
- 第一行的方法,是設定aCattle,也就是Cattle或者Bull對象的屬性,而後調用saySomething方法,實現控制檯的打印輸出。
- 第二行的方法,是把咱們定義的DoProxy類裏面的一些變量進行賦值。
- 第三行的方法,是調用doWithCattleId方法。
- 第四行的方法,是調用了函數指針的方法。
- 好的,咱們把DoProxy.h的內容介紹完了,讓咱們打開DoProxy.m。
5.8,DoProxy.m的代碼說明
- 有了DoProxy.h的說明,同窗們理解DoProxy.m將是一件很是輕鬆的事情,讓咱們堅持一下把這個輕鬆的事情搞定。因爲篇幅所限,筆者在這裏的講解將會省略掉非本章的內容。
- DoProxy.m代碼以下:
- 1#import"DoProxy.h"
2#import"Cattle.h"
3#import"Bull.h"
4
5@implementationDoProxy
6-(void)setAllIVars
7{
8cattle[0]=[Cattlenew];
9
10bullClass=NSClassFromString(BULL_CLASS);
11cattle[1]=[bullClassnew];
12cattle[2]=[bullClassnew];
13
14say=@selector(saySomething);
15skin=NSSelectorFromString(SET_SKIN_COLOR);
16}
17-(void)SELFuncs
18{
19[selfdoWithCattleId:cattle[0]colorParam:@"brown"];
20[selfdoWithCattleId:cattle[1]colorParam:@"red"];
21[selfdoWithCattleId:cattle[2]colorParam:@"black"];
22[selfdoWithCattleId:selfcolorParam:@"haha"];
23}
24-(void)functionPointers
25{
26setSkinColor_Func=(void(*)(id,SEL,NSString*))[cattle[1]methodForSelector:skin];
27//IMPsetSkinColor_Func=[cattle[1]methodForSelector:skin];
28say_Func=[cattle[1]methodForSelector:say];
29setSkinColor_Func(cattle[1],skin,@"verbose");
30NSLog(@"Runningasafunctionpointerwillbemoreefficiency!");
31say_Func(cattle[1],say);
32}
33-(void)doWithCattleId:(id)aCattlecolorParam:(NSString*)color
34{
35if(notFirstRun==NO)
36{
37NSString*myName=NSStringFromSelector(_cmd);
38NSLog(@"Runninginthemethodof%@",myName);
39notFirstRun=YES;
40}
41
42NSString*cattleParamClassName=[aCattleclassName];
43if([cattleParamClassNameisEqualToString:BULL_CLASS]||
44[cattleParamClassNameisEqualToString:CATTLE_CLASS])
45{
46[aCattlesetLegsCount:4];
47if([aCattlerespondsToSelector:skin])
48{
49[aCattleperformSelector:skinwithObject:color];
50}
51else
52{
53NSLog(@"Hi,Iama%@,havenotsetSkinColor!",cattleParamClassName);
54}
55[aCattleperformSelector:say];
56}
57else
58{
59NSString*yourClassName=[aCattleclassName];
60NSLog(@"Hi,youarea%@,butIlikecattleorbull!",yourClassName);
61}
62}
63@end
- 第10行代碼是經過一個預約義的宏BULL_CLASS取得Bull的Class變量。
- 第11和12行代碼是使用bullClass來初始化咱們的cattle實例變量數組的第2和第3個元素。
- 第14行是經過@selector函數來取得saySomething的SEL變量。
- 第15行是經過向NSSelectorFromString傳遞預約義的宏SET_SKIN_COLOR來取得setSkinColor的SEL變量。
- 第22行,筆者打算「戲弄」一下doWithCattleId,向傳遞了不合適的參數。
- 第26行,筆者取得了傳統的C語言的函數指針,也是使用了第5.5節所述的第一種取得的方法。
- 第28行,筆者經過5.5節所述的第二種取得的方法獲得了函數指針say_Func。
- 第29行和31行分別執行了分別在第26行和28行取得的函數指針。
- 第35行是一個BOOL型的實例變量notFirstRun。當對象被初始化以後,確省的值是NO。第一次執行完畢以後,咱們把這個變量設定成爲YES,這樣就保證了花括號裏面的代碼只被執行一次。
- 第37行咱們經過_cmd取得了doWithCattleId這個方法名字用於輸出。固然同窗們在設計方法的提供給別人使用的時候,爲了防止使用方法的人把這個方法自己傳遞進來形成死循環,須要使用_cmd這個系統隱藏的變量判斷一下。筆者在這裏沒有作出判斷,這樣寫從理論上來講存在必定的風險。
- 第42行,咱們經過向對象發送className消息來取得這個對象的類的名字。
- 第43行和第44行,咱們經過NSString的方法isEqualToString來判斷取得的類的名字是否在咱們事先想象的範圍以內,咱們只但願接受Bull或者Cattle類的對象。
- 第46行,原本咱們想經過SEL的方式來進行這個牛股的設定,可是因爲它的參數不是從NSObject繼承下來的,因此咱們沒法使用。咱們會有辦法解決這個問題的,咱們將在後面的章節裏面介紹解決這個問題的方法。
- 第47行的代碼,有一個很是重要NSObject的方法respondsToSelector,經過向對象發送這個消息,加上一個SEL,咱們能夠知道這個對象是否能夠相應這個SEL消息。因爲咱們的Cattle沒法相應setSkinColor消息,因此若是對象是Cattle類生成的話,if語句就是NO因此花括號裏面的內容不會獲得執行。
- 第59行,咱們經過類的名字發現了一個假冒的Cattle,咱們把這個假冒的傢伙給揪出來,而後實現了屏幕打印。
5.9,本章總結
本章給同窗們介紹了幾個新的數據類型,以及使用方法,這些數據類型分別是BOOL,SEL,Class,IMP。
本章的內容很重要,但願同窗們花一點時間仔細的理解一下。應該說,本章的內容有些使人難以理解,或者說知道了SEL,Class,IMP以後也許不知道如何使用,遇到什麼狀況的時候須要使用。不過在學習Objective-C的初級階段,不知道這些也沒有關係,可是SEL,Class,IMP的概念須要掌握,不然當你遇到別人寫的質量比較高的代碼或者蘋果官方的技術文檔的時候,你會以爲理解起來比較吃力。
6,NSObject的奧祕
本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
在上一章裏面,筆者向你們介紹了在Objective-C裏面的幾個很是重要的概念,簡單的說就是SEL,Class和IMP。咱們知道Objective-C是C語言的擴展,有了這3個概念還有咱們之前講過的繼承和封裝的概念,Objective-C發生了翻天覆地的變化,既兼容C語言的高效特性又實現了面向對象的功能。
Objective-C從本質上來講,仍是C語言的。那麼內部到底是怎樣實現SEL,Class和IMP,還有封裝和繼承的?爲了解答這個問題,筆者決定在本章向你們概要的介紹一下Objective-C的最主要的一個類,NSObject。
不過說實在話,若是同窗們以爲本章的內容比較晦澀難懂的話,不閱讀本章的內容絲絕不會對寫程序產生任何不良的影響,可是若是掌握了本章的內容的話,對加深對Objective-C的理解,對於從此筆者將要講述的內容而言,將會是一個極大的促進。
6.1,本章程序的執行結果
在本章裏面,咱們將要繼續使用咱們在前面幾章已經構築好的類Cattle和Bull。因爲在如今的Xcode版本里面,把一些重要的東西好比說Class的原型定義都放到了LIB文件裏面,因此這些東西的具體的定義,對於咱們來講是不可見的。
咱們首先把第4章的代碼打開,而後打開「Cattle.h」文件,把鼠標移動到「NSObject」上面,單擊鼠標右鍵,在彈出菜單裏面選擇「JumptoDefinition」。而後會彈出一個小菜單,咱們選擇「interfaceNSObject」。咱們能夠看到以下代碼
@interfaceNSObject<NSObject>{
Classisa;
咱們知道了,所謂的NSObject裏面只有一個變量,就是Class類型的isa。isa的英文的意思就是isapointer的意思。也就是說NSObject裏面只有一個實例變量isa。好的,咱們須要知道Class是一個什麼東西,咱們把鼠標移動到「Class」上面,單擊鼠標右鍵,在彈出菜單裏面選擇「JumptoDefinition」,咱們看到了以下的代碼:
typedefstructobjc_class*Class;
typedefstructobjc_object{
Classisa;
}*id;
...
咱們在這裏知道了,Class其實是一個objc_class的指針類型,咱們把鼠標移動到「objc_class」上面,單擊鼠標右鍵,在彈出菜單裏面選擇「JumptoDefinition」,發現咱們仍是在這個窗口裏面,Xcode並無把咱們帶到objc_class的定義去,因此咱們無從知道objc_class內部到底是一個什麼樣的東西。
筆者順便提一下,你們也許注意到了id的定義,id其實是objc_object結構的一個指針,裏面只有一個元素那就是Class。那麼根據上面咱們看到的,所謂的id就是objc_class的指針的指針。讓咱們回憶一下下面的代碼:
idcattle=[Cattlenew];
這句話是在初始化和實例話cattle對象,這個過程,實際上能夠理解爲,runtime爲咱們初始化好了Class的指針,而且把這個指針返回給咱們。咱們初始化對象完成了以後,實際上咱們獲得的對象就是一個指向這個對象的Class指針。
讓咱們在回過頭來講說這個神祕的Class,咱們沒法在Xcode裏面看到Class也就是objc_class的定義。慶幸的是這部分的定義是GCC代碼,是開源的。筆者下載了開源的代碼以後,把開源的代碼做了一些小小的調整,而後把Class的定義等等放到了咱們的工程文件裏面去,經過類型轉化以後,咱們終於能夠看到Class,SEL,還有isa等等過去對咱們來講比較「神祕」的東西的真正面目。
咱們在前面幾章裏面在每個章的第一節裏面都要介紹一下本章程序執行結果的屏幕拷貝,本章也是同樣,可是本章的執行結果很是簡單。由於對於本章而言重點應該是放在對NSObject機制的理解上。
圖6-1,本章程序運行結果
你們看到本章程序的運行結果的屏幕拷貝的時候,也許會以爲很無趣,由於單單從結果畫面,咱們沒有發現任何使人感到頗有興趣的東西,相反,都是同窗們已經很熟悉的一些老面孔。可是本章所要講述的東西也許是同窗們在其餘語言裏面歷來沒有遇到過的東西,這些東西將會使人感到新鮮和激動。
6.2,實現步驟
- 第一步,按照咱們在第2章所述的方法,新建一個項目,項目的名字叫作06-NSObject。若是你是第一次看本篇文章,請到這裏參看第二章的內容。
- 第二步,按照咱們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的「Cattle.h」,「Cattle.m」,「Bull.h」還有「Bull.m」導入本章的項目裏面。若是你沒有第4章的代碼,請到這裏下載。若是你沒有閱讀第4章的內容,請參看這裏。
- 第三步,把鼠標移動到項目瀏覽器上面的「Source」上面,而後在彈出的菜單上面選擇「Add」,而後在子菜單裏面選擇「NewFile」,而後在新建文件對話框的左側最下面選擇「Other」,而後在右側窗口選擇「EmptyFile」,選擇「Next」,在「NewFile」對話框裏面的「FileName」欄內輸入「MyNSObject.h」。而後輸入(或者是拷貝也能夠,由於這是C的代碼,若是你很熟悉C語言的話,能夠拷貝一下節省時間)以下代碼:
- #include<stddef.h>
typedefconststructobjc_selector
{
void*sel_id;
constchar*sel_types;
}*MySEL;
typedefstructmy_objc_object{
structmy_objc_class*class_pointer;
}*myId;
typedefmyId(*MyIMP)(myId,MySEL,);
typedefchar*STR;/*Stringalias*/
typedefstructmy_objc_class*MetaClass;
typedefstructmy_objc_class*MyClass;
structmy_objc_class{
MetaClassclass_pointer;
structmy_objc_class*super_class;
constchar*name;
longversion;
unsignedlonginfo;
longinstance_size;
structobjc_ivar_list*ivars;
structobjc_method_list*methods;
structsarray*dtable;
structmy_objc_class*subclass_list;
structmy_objc_class*sibling_class;
structobjc_protocol_list*protocols;
void*gc_object_type;
};
typedefstructobjc_protocol{
structmy_objc_class*class_pointer;
char*protocol_name;
structobjc_protocol_list*protocol_list;
structobjc_method_description_list*instance_methods,*class_methods;
}Protocol;
typedefvoid*retval_t;
typedefvoid(*apply_t)(void);
typedefunionarglist{
char*arg_ptr;
chararg_regs[sizeof(char*)];
}*arglist_t;
typedefstructobjc_ivar*Ivar_t;
typedefstructobjc_ivar_list{
intivar_count;
structobjc_ivar{
constchar*ivar_name;
constchar*ivar_type;
intivar_offset;
}ivar_list[1];
}IvarList,*IvarList_t;
typedefstructobjc_method{
MySELmethod_name;
constchar*method_types;
MyIMPmethod_imp;
}Method,*Method_t;
typedefstructobjc_method_list{
structobjc_method_list*method_next;
intmethod_count;
Methodmethod_list[1];
}MethodList,*MethodList_t;
structobjc_protocol_list{
structobjc_protocol_list*next;
size_tcount;
Protocol*list[1];
};
- 第四步,打開06-NSObject.m文件,輸入以下代碼而且保存
- #import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
#import"MyNSObject.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle=[Cattlenew];
idredBull=[Bullnew];
SELsetLegsCount_SEL=@selector(setLegsCount:);
IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
IMPredBull_setLegsCount_IMP=[redBullmethodForSelector:setLegsCount_SEL];
[cattlesetLegsCount:4];
[redBullsetLegsCount:4];
[redBullsetSkinColor:@"red"];
Classcattle_class=cattle->isa;
MyClassmy_cattle_class=cattle->isa;
SELsay=@selector(saySomething);
IMPcattle_sayFunc=[cattlemethodForSelector:say];
cattle_sayFunc(cattle,say);
ClassredBull_class=redBull->isa;
MyClassmy_redBull_class=redBull->isa;
IMPredBull_sayFunc=[redBullmethodForSelector:say];
redBull_sayFunc(redBull,say);
[pooldrain];
return0;
}
- 第五步,在06-NSObject.m文件的窗口的「[pooldrain];」代碼的左側單擊一下窗口的邊框,確認一下是否出現一個藍色的小棒棒,若是有的話那麼斷點被選擇好了。如圖6-2所示
- 圖6-2,選擇執行斷點
- 第六步,選擇Xcode上面的菜單的「Run」,而後選擇「Debuger」,在Debuger窗口裏面選擇「BuildandGo」。
- 好的,你們就停在這裏,不要作其餘的操做,咱們把程序中斷在程序幾乎執行到最後的斷點上,咱們將要經過Debuger來看看Objective-C內部究竟發生了什麼樣的奇妙的魔法。
- 注意在從編譯到執行的過程中,會出現一些警告。因爲本章程序指示用來闡述一些NSObject內部的東西,因此請忽略掉這些警告。固然,咱們在寫本身的程序的時候,編譯產生的警告通常是不能被忽略的。
6.3,超類方法的調用
- 咱們如今打開「06-NSObject.m」文件,發現下面的代碼:
- SELsetLegsCount_SEL=@selector(setLegsCount:);
IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
IMPredBull_setLegsCount_IMP=[redBullmethodForSelector:setLegsCount_SEL];
- 這一段代碼,對同窗們來講不是什麼新鮮的內容了,咱們在第5章裏面已經講過,這個是SEL和IMP的概念。咱們在這裏取得了cattle對象和redBull對象的setLegsCount:的函數指針。
- 若是你們如今已經不在Debuger裏面的話,那麼請選擇Xcode菜單裏面的,「Run」而後選擇「Debuger」。
- 咱們注意到在Debuger裏面,cattle_setLegsCount_IMP的地址和redBull_setLegsCount_IMP是徹底同樣的,如圖6-3所示:
- 圖6-3,cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的地址。
- 注意因爲環境和執行的時候的內存狀況不一樣,因此同窗們的電腦上顯示的地址的數值可能和圖6-3的數值不同。
- cattle_setLegsCount_IMP和redBull_setLegsCount_IMP的地址徹底同樣,說明他們使用的是相同的代碼段。這種結果是怎樣產生的呢?你們請打開「MyNSObject.h」,參照下列代碼:
- structmy_objc_class{
MetaClassclass_pointer;
structmy_objc_class*super_class;
constchar*name;
longversion;
unsignedlonginfo;
longinstance_size;
structobjc_ivar_list*ivars;
structobjc_method_list*methods;
structsarray*dtable;
structmy_objc_class*subclass_list;
structmy_objc_class*sibling_class;
structobjc_protocol_list*protocols;
void*gc_object_type;
};
- 筆者在這裏把開源代碼的名字的定義加上了「my_」前綴,僅僅是爲了區分一下。「MyNSObject.h」裏面的代碼問題不少,筆者歷來沒有也不會在實際的代碼裏面使用這段代碼,使用這些代碼的主要目的是爲了向你們講解概念,請你們忽略掉代碼裏面的種種問題。
- 咱們注意到這裏的methods變量,裏面包存的就是類的方法名字(SEL)定義,方法的指針地址(IMP)。當咱們執行
- IMPcattle_setLegsCount_IMP=[cattlemethodForSelector:setLegsCount_SEL];
- 的時候,runtime會經過dtable這個數組,快速的查找到咱們須要的函數指針,查找函數的定義以下:
- __inline__IMP
objc_msg_lookup(idreceiver,SELop)
{
if(receiver)
returnsarray_get(receiver->class_pointer->dtable,(sidx)op);
else
returnnil_method;
- 好的,如今咱們的cattle_setLegsCount_IMP沒有問題了,那麼redBull_setLegsCount_IMP怎麼辦?在Bull類裏面咱們並無定義實例方法setLegsCount:,因此在Bull的Class裏面,runtime難道找不到setLegsCount:麼?答案是,是的runtime直接找不到,由於咱們在Bull類裏面根本就沒有定義setLegsCount:。
- 可是,從結果上來看很明顯runtime聰明的找到了setLegsCount:的地址,runtime是怎樣找到的?答案就在:
- structmy_objc_class*super_class;
- 在本身的類裏面沒有找到的話,runtime會去Bull類的超類cattle裏面去尋找,慶幸的是它成功的在cattle類裏面runtime找到了setLegsCount:的執行地址入口,因此咱們獲得了redBull_setLegsCount_IMP。redBull_setLegsCount_IMP和cattle_setLegsCount_IMP都是在Cattle類裏面定義的,因此他們的代碼的地址也是徹底同樣的。
- 咱們如今假設,若是runtime在cattle裏面也找不到setLegsCount:呢?沒有關係,cattle裏面也有超類的,那就是NSObject。因此runtime會去NSObject裏面尋找。固然,NSObject不會神奇到能夠預測咱們要定義setLegsCount:因此runtime是找不到的。
- 在這個時候,runtime並無放棄最後的努力,再沒有找到對應的方法的時候,runtime會向對象發送一個forwardInvocation:的消息,而且把原始的消息以及消息的參數打成一個NSInvocation的一個對象裏面,做爲forwardInvocation:的惟一的參數。forwardInvocation:自己是在NSObject裏面定義的,若是你須要重載這個函數的話,那麼任何試圖向你的類發送一個沒有定義的消息的話,你均可以在forwardInvocation:裏面捕捉到,而且把消息送到某一個安全的地方,從而避免了系統報錯。
- 筆者沒有在本章代碼中重寫forwardInvocation:,可是在重寫forwardInvocation:的時候必定要注意避免消息的循環發送。好比說,同窗們在A類對象的forwardInvocation裏面,把A類不能響應的消息以及消息的參數發給B類的對象;同時在B類的forwardInvocation裏面把B類不能響應的消息發給A類的時候,容易造成死循環。固然一我的寫代碼的時候不容易出現這個問題,當你在一個工做小組裏面作的時候,若是你重寫forwardInvocation:的時候,須要和小組的其餘人達成共識,從而避免循環調用。
6.4,重載方法的調用
- 讓咱們繼續關注「06-NSObject.m」文件,請你們參考一下下面的代碼:
- 1Classcattle_class=cattle->isa;
2MyClassmy_cattle_class=cattle->isa;
3SELsay=@selector(saySomething);
4IMPcattle_sayFunc=[cattlemethodForSelector:say];
5cattle_sayFunc(cattle,say);
6
7ClassredBull_class=redBull->isa;
8MyClassmy_redBull_class=redBull->isa;
9
10IMPredBull_sayFunc=[redBullmethodForSelector:say];
11redBull_sayFunc(redBull,say);
- 本節的內容和6.3節的內容比較相似,關於代碼部分筆者認爲就不須要解釋了,若是同窗們有所不熟悉的話,能夠參考一下第5章的內容。
- 在咱們的Cattle類和Bull類裏面,都有saySometing這個實例方法。咱們知道只要方法的定義相同,那麼它們的SEL是徹底同樣的。咱們根據一個SELsay,在cattle和redBull對象裏面找到了他們的函數指針。根據6.3節的講述,咱們知道當runtime接收到尋找方法的時候,會首先在這個類裏面尋找,尋找到了以後尋找的過程也就結束了,同時把這個方法的IMP返回給咱們。因此,在上面的代碼裏面的cattle_sayFunc和redBull_sayFunc應該是不同的,如圖6-4所示:
- 圖6-4,cattle_sayFunc和redBull_sayFunc的地址
6.5,超類和子類中的Class
- 在類進行內存分配的時候,對於一個類而言,runtime須要找到這個類的超類,而後把超類的Class的指針的地址賦值給isa裏面的super_class。因此,咱們的cattle裏面的Class應該和redBull裏面的Class裏面的super_class應該是徹底相同的,請參照圖6-5:
- 圖6-5,cattle裏面的Class和redBull裏面的Class裏面的super_class
6.6,實例變量的內存分配的位置
- 咱們先來回憶一下對象是怎樣被建立的。建立對象的時候,類的內容須要被調入到內存當中咱們稱之爲內存分配(Allocation),而後須要把實體變量進行初始化(Initialization),當這些步驟都結束了以後,咱們的類就被實例化了,咱們把實例化完成的類叫作對象(Object)。
- 對於內存分配的過程,runtime須要知道分配多少內存還有各個實例變量的位置。咱們回到「MyNSObject.h」,參照以下代碼:
- 1typedefstructobjc_ivar*Ivar_t;
2typedefstructobjc_ivar_list{
3intivar_count;
4structobjc_ivar{
5constchar*ivar_name;
6constchar*ivar_type;
7intivar_offset;
8}ivar_list[1];
9}IvarList,*IvarList_t;
- 咱們仔細看看第5行的ivar_name,顧名思義這個是實例變量的名字,第6行的ivar_type是實例變量的類型,第7行的ivar_offset,這個就是位置的定義。runtime從類的isa裏面取得了這些信息以後就知道了如何去分配內存。咱們來看看圖6-6:
- 圖6-6,實例變量在內存中的位置
- 在cattle裏面,咱們看到了第一個實例變量是isa,第二個就是咱們定義的legsCount。其中isa是超類的變量,legsCount是Cattle類的變量。咱們能夠看出來,老是把超類的變量放在前頭,而後是子類的變量。
- 那麼對於redBull而言是什麼樣子呢?咱們來看看圖6-7
- 圖6-7,redBull裏面的實例變量的位置
- 咱們經過圖6-7能夠發現redBull的Class裏面的skinColor的位置偏移是8,很明顯,runtime爲isa和legsCount預留了2個位置。
6.7本章總結
- 很是感謝你們!
- 在本章裏面,筆者經過一個小小的「把戲」爲同窗們揭開了NSObject的神祕的面紗。本章的內容,雖然對理解Objective-C不是必需的,可是對之後的章節的內容的理解會有一個很是好的輔助做用,但願同窗們花費一點點心思和時間閱讀一下。
7,對象的初始化以及實例變量的做用域
- 本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
- 到目前爲止,咱們都使用的是下列方式建立對象
- [類名new];
- 這種new的方式,其實是一種簡化的方式。筆者在這裏總結一下前面幾章裏面曾經提到過關於建立對象的2個步驟:
- 第一步是爲對象分配內存也就是咱們所說的allocation,runtime會根據咱們建立的類的信息來決定爲對象分配多少內存。類的信息都保存在Class裏面,runtime讀取Class的信息,知道了各個實例變量的類型,大小,以及他們的在內存裏面的位置偏移,就會很容易的計算出須要的內存的大小。分配內存完成以後,實際上對象裏面的isa也就被初始化了,isa指向這個類的Class。類裏面的各個實例變量,包括他們的超類裏面的實例變量的值都設定爲零。
- 須要注意的是,分配內存的時候,不須要給方法分配內存的,在程序模塊總體執行的時候方法部分就做爲代碼段的內容被放到了內存當中。對象的內容被放到了數據段當中,編譯好的方法的彙編代碼被放到了代碼段當中。在ObjectiveC裏面,分配內存使用下列格式:
- id對象名=[類名alloc];
- NSObject已經爲咱們提供了諸如計算內存空間大小以及初始化isa還有把各個實例變量清零,毫無疑問NSObject已經很是出色的完成了內存分配的工做,在通常狀況下,咱們不須要重寫alloc方法。
- 第二步是要對內存進行初始化也就是咱們所說的Initialization。初始化指的是對實例變量的初始化。雖然在alloc方法裏面已經把各個實例變量給清零了,可是在不少狀況下,咱們的實例變量不能是零(對於指針的實例變量而言,就是空指針)的,這樣就須要咱們對實例變量進行有意義的初始化。
- 按照Objective-C的約定,當初始化的時候不須要參數的話,就直接使用init方法來初始化:
- [對象名字init];
- init是一個定義在NSObject裏面的一個方法,NSObject明顯沒法預測到派生類的實例變量是什麼,因此同窗們在本身的類裏面須要重載一下init方法,在init方法裏面把實例變量進行初始化。
- 可是,須要強調的是,因爲某種緣由咱們的init也許失敗了,好比說咱們須要讀取CNBLOGS.COM的某個RSS,用這個RSS來初始化咱們的對象,可是因爲用戶的網絡鏈接失敗因此咱們的init也許會失敗,在手機應用當中的一些極端的狀況下好比說有同窗寫一個讀取網頁內容的程序,在網頁內容很是大的時候,那麼alloc也有可能會失敗,爲了能夠方便的捕獲這些失敗,因此咱們在程序當中須要把上面的過程寫在一塊兒:
- id對象名=[[類名alloc]init];
if(對象名)
else
- 加上了上面的if語句咱們的初始化過程就是完美的,固然咱們有的時候不須要這個if語句。當咱們的alloc和init永遠不會失敗的時候。關於初始化的時候的錯誤捕獲,筆者將在後面的章節裏面論述。
- 爲了咱們寫程序方便和簡潔,在建立一個從NSObject派生的類的對象的時候,蘋果公司把alloc和init簡化成爲new,咱們在程序代碼當中使用任何一種方式都是能夠的,具體怎麼寫是同窗們的喜愛和自由。
- 到這裏,有同窗會問,若是咱們的init須要參數怎麼辦?按照Objective-C的約定,咱們須要使用initWith...。也就是帶參數的變量初始化,這個也是本章的主要內容。
- 本章在講述initWith的同時,也將會順便的給你們介紹一下實例變量的做用域。
7.1,本章程序的執行結果
- 在本章裏面,咱們將要繼續使用咱們在第4章已經構築好的類Cattle和Bull。從通常的面向對象的角度上來講,是不鼓勵咱們改寫已經生效的代碼的。可是本章的目的是爲了使同窗們能夠很好的理解主題,因此筆者在這裏暫時違反一下規則改寫了一下Cattle類,在裏面追加了initWith方法,筆者也在Cattle類裏面追加了一些實例變量爲了闡述實例變量的做用域的問題。因爲在Cattle類裏面筆者追加了一些東西,因此在Bull類裏面改寫了saySomething這個函數,讓咱們的Bull能夠說更多的內容。咱們的redBull是這樣說的:
- 圖7-1,本章程序的執行結果
- 本章程序代碼晴點擊這裏下載。
- 再次強調在實際的編程過程當中,尤爲是寫大型程序多人合做的時候,除非發現BUG,不然不要改寫已經生效的代碼。這樣會產生一些意想不到的結果,從而使其餘的弟兄們或者姐妹們對你充滿怨言。
7.2,實現步驟
- 第一步,按照咱們在第2章所述的方法,新建一個項目,項目的名字叫作07-InitWithAndIvarScope。若是你是第一次看本篇文章,請到這裏參看第二章的內容。
- 第二步,按照咱們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的「Cattle.h」,「Cattle.m」,「Bull.h」還有「Bull.m」,導入本章的項目裏面。而後把第6章裏面的「MyNSObject.h」也導入到項目當中。
- 第三步,打開「Cattle.h」,修改爲爲下面的代碼而且保存:
- #import<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
@private
boolgender;//male=YESfemale=NO
@protected
inteyesCount;
@public
NSString*masterName;
}
-(void)saySomething;
-(void)setLegsCount:(int)count;
-(id)initWithLegsCount:(int)theLegsCount
gender:(bool)theGender
eyesCount:(int)theEyesCount
masterName:(NSString*)theMasterName;
@end
- 第4步,打開「Cattle.m」,修改爲下面的代碼而且保存:
- #import"Cattle.h"
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
-(id)init
{
[superinit];
return[selfinitWithLegsCount:4
gender:YES
eyesCount:2
masterName:@"somebody"];
}
-(id)initWithLegsCount:(int)theLegsCount
gender:(bool)theGender
eyesCount:(int)theEyesCount
masterName:(NSString*)theMasterName
{
legsCount=theLegsCount;
gender=theGender;
eyesCount=theEyesCount;
masterName=theMasterName;
returnself;
}
@end
- 第五步,打開「Bull.m」,,修改爲下面的代碼而且保存:
- #import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
NSLog(@"Ihave%deyes,mymasteris%@.",eyesCount,masterName);
//Listbelowisillegal
//NSLog(@"Mygenderis%@",gender?@"male":@"female");
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
@end
- 第六步,打開「07-InitWithAndIvarScope.m」,修改爲下面的代碼而且保存:
- #import<Foundation/Foundation.h>
#import"Bull.h"
#import"Cattle.h"
#import"MyNSObject.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
Bull*redBull=[[Bullalloc]initWithLegsCount:4
gender:YES
eyesCount:2
masterName:@"thatcowboy"];
[redBullsetSkinColor:@"red"];
[redBullsaySomething];
//legal,butnotgood
redBull->masterName=@"thatcowgirl";
//legal,butbad
//redBull->eyesCount=3;
//Tryingtoaccessaprivateivar,VERYbadthing
//MyClassbullClass=redBull->isa;
bool*redBullGender=(bool*)(redBull)+8;
NSLog(@"Mygenderis%@",*redBullGender?@"male":@"female");
[pooldrain];
return0;
}
- 第七步,選擇屏幕上方菜單裏面的「Run」,而後選擇「Console」,打開了Console對話框以後,選擇對話框上部中央的「BuildandGo」,若是不出什麼意外的話,那麼應該出現入圖7-1所示的結果。若是出現了什麼意外致使錯誤的話,那麼請仔細檢查一下你的代碼。若是通過仔細檢查發現仍是不能執行的話,能夠到這裏下載筆者爲同窗們準備的代碼。若是筆者的代碼仍是不能執行的話,請告知筆者。
7.3,實例變量的做用域(Scope)
- 對於Objective-C裏面的類的實例變量而言,在編譯器的範圍裏面,是有做用域的。和其餘的語言同樣,Objective-C也支持public,private還有protected做用域限定。
- 若是一個實例變量沒有任何的做用域限定的話,那麼缺省就是protected。
- 若是一個實例變量適用於public做用域限定,那麼這個實例變量對於這個類的派生類,還有類外的訪問都是容許的。
- 若是一個實例變量適用於private做用域限定,那麼僅僅在這個類裏面才能夠訪問這個變量。
- 若是一個實例變量適用於protected做用域限定,那麼在這個類裏面和這個類的派生類裏面能夠訪問這個變量,在類外的訪問是不推薦的。
- 咱們來看看「Cattle.h」的代碼片段:
- 1intlegsCount;
2@private
3boolgender;//male=YESfemale=NO
4@protected
5inteyesCount;
6@public
7NSString*masterName;
- 第一行的legsCount的前面沒有任何做用域限定,那麼它就是protected的。
- 第二行是在說從第二行開始的實例變量的定義爲private的,和其餘的關鍵字同樣,Objective-C使用@來進行編譯導向。
- 第三行的gender的做用域限定是private的,因此它適用於private做用域限定。
- 第四行是在說從第四行開始的實例變量的定義爲protected的,同時第二行的private的聲明做廢。
- 第五行的eyesCount的做用域限定是protected的,因此它適用於protected做用域限定。
- 第六行是再說從第六行開始的實例變量的定義爲public的,同時第四行的protected的聲明做廢。
- 第七行的masterName的做用域限定是public的,因此它適用於public做用域限定。
- 咱們再來看看在派生類當中,private,protected還有public的表現。Bull類繼承了Cattle類,筆者改寫了一下「Bull.m」用來講明做用域的問題,請參看下面的代碼:
- 1-(void)saySomething
2{
3NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
4NSLog(@"Ihave%deyes,mymasteris%@.",eyesCount,masterName);
5//Listbelowisillegal
6//NSLog(@"Mygenderis%@",gender?@"male":@"female");
7}
- 咱們來看看第3還有第4行代碼,咱們能夠訪問legsCount,eyesCount還有masterName。
- 在第6行代碼當中,咱們試圖訪問gender這個Cattle的私有(private)屬性,這行代碼產生了編譯錯誤,因此咱們不得不註釋掉第6行代碼。
- 好的,咱們再來看看類的外部private,protected還有public的表現。請同窗們打開「07-InitWithAndIvarScope.m」,參考一下下面的代碼:
- 1//legal,butnotgood
2redBull->masterName=@"thatcowgirl";
3//legal,butbad
4//redBull->eyesCount=3;
5
6//Tryingtoaccessaprivateivar,VERYbadthing
7//MyClassbullClass=redBull->isa;
8bool*redBullGender=(bool*)(redBull)+8;
9NSLog(@"Mygenderis%@",*redBullGender?@"male":@"female");
- 在第二行裏面,咱們訪問了masterName,因爲在Cattle裏面masterName是public的,Bull繼承了Cattle,因此咱們能夠直接訪問masterName。可是這不是一種好的習慣,由於這不符合面向對象的基本思想。實際上,若是沒有特殊的理由,咱們不須要使用public的。
- 第四行,咱們試圖在類的外邊訪問protected變量eyesCount,在這裏筆者的Xcode只是輕輕的給了一個警告,編譯成功而且能夠運行。一樣,這種在類的外邊訪問類的protected變量是一個很糟糕的作法。
- 咱們還記得在Bull的saySomething裏面咱們曾經試圖訪問過gender,可是編譯器無情的阻止了咱們,由於gender是私有的。可是,這僅僅是編譯器阻止了咱們,當咱們有足夠的理由須要在類的外邊訪問private實例變量的時候,咱們仍是能夠經過一些強硬的方法合法的訪問私有變量的,咱們的方法就是使用指針偏移。
- 咱們首先回憶一下第6章的6.6節的內容,isa裏面保存了對象裏面的實例變量相對於對象首地址的偏移量,咱們獲得了這個偏移量以後就能夠根據對象的地址來得到咱們所須要的實例變量的地址。在正常狀況下,咱們須要經過訪問類自己和它的超類的ivars來得到偏移量的,可是筆者在這裏偷了一個懶,先使用第七行的代碼MyClassbullClass=redBull->isa;經過Debugger得到gender的偏移量,數值爲8。而後在第8行裏面,筆者經過使用指針偏移取得了gender的指針而後在第9行實現了輸出。
- 因而可知,在Objective-C裏面,所謂的private還有protected只是一個Objective-C強烈推薦的一個規則,咱們須要按照這個規則來編寫代碼,可是若是咱們違反了這個規則,編譯器沒有任何方法阻止咱們。
- 筆者認爲在類的外部直接訪問任何實例變量,無論這個實例變量是public,private仍是protected都是一個糟糕的作法,這樣會明顯的破壞封裝的效果,儘管這樣對編譯器來講是合法的。
7.4,initWith...
- NSObject爲咱們準備的不帶任何參數的init,咱們的類裏面沒有實例變量,或者實例變量能夠都是零的時候,咱們能夠使用NSObject爲咱們準備的缺省的init。當咱們的實例變量不能爲零,而且這些實例變量的初始值能夠在類的初始化的時候就能夠肯定的話,咱們能夠重寫init,而且在裏面爲實例變量初始化。
- 可是在不少時候,咱們沒法預測類的初始化的時候的實例變量的初始值,同時NSObject明顯沒法預測到咱們須要什麼樣的初始值,因此咱們須要本身初始化類的實例變量。
- 請同窗們打開「Cattle.m」,咱們參考一下下面的代碼:
- 1-(id)init
2{
3[superinit];
4return[selfinitWithLegsCount:4
5gender:YES
6eyesCount:2
7masterName:@"somebody"];
8}
9-(id)initWithLegsCount:(int)theLegsCount
10gender:(bool)theGender
11eyesCount:(int)theEyesCount
12masterName:(NSString*)theMasterName
13{
14legsCount=theLegsCount;
15gender=theGender;
16eyesCount=theEyesCount;
17masterName=theMasterName;
18returnself;
19}
- 從第3行到第7行,筆者重寫了一下init。在init裏面,筆者給經過調用initWith,給類的各個實例變量加上了初始值。這樣寫是很必要的,由於未來的某個時候,也許有人(或者是本身)很冒失的使用init來初始化對象,而後就嘗試使用這個對象,若是咱們沒有重寫init,那麼也許會出現一些意想不到的事情。
- 從第9行到第19行,是咱們本身定義的initWith,代碼比較簡單,筆者就不在這裏贅述了。須要注意的一點是,筆者沒有在這裏調用[superinit];。緣由是Cattle的超類就是NSObject,初始化的過程就是初始化實例變量的過程,runtime已經爲咱們初始化好了NSObject的惟一實例變量isa,也就是Cattle的類的信息,因此咱們不須要調用[superinit];。在某些時候,超類的變量須要初始化的時候,請同窗們在子類的init或者initWith裏面調用[superinit];。
- 請同窗們再次打開「07-InitWithAndIvarScope.m」,參考下面的代碼片段:
1Bull*redBull=[[Bullalloc]initWithLegsCount:4
2gender:YES
3eyesCount:2
4masterName:@"thatcowboy"];
5[redBullsetSkinColor:@"red"];
6[redBullsaySomething];
從第1行到第4行就是調用的initWith來初始化咱們的redBull。
7.5,本章總結
很是感謝你們對筆者的支持!
咱們在本章裏面介紹了2個比較輕鬆的話題,一個是實例變量的做用域,這個概念筆者我的認爲對有一點面向對象編程經驗的人來講,不是什麼新鮮的概念了。可是須要注意的是,Objective-C並無強制咱們遵照它的規則,他仍舊爲咱們提供了違反規則的機會,這一點上根C++比較相似。只要支持指針,就沒法避免使用者違反規則。事務都是一分爲二的,當咱們獲得了訪問任何變量的自由以後,咱們必須爲訪問這些變量承擔後果。
第二個話題就是initWith。和其餘的面向對象的語言不一樣,Objective-C沒有構造函數,它經過init還有initWith來初始化變量,咱們應該根據具體狀況進行具體的分析,從而編寫咱們的init還有initWith方法。
8,類方法以及私有方法
本系列講座有着很強的先後相關性,若是你是第一次閱讀本篇文章,爲了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裏。
Objective-C裏面區別於實例方法,和Java或者C++同樣,也支持類方法。類方法(ClassMethod)有時被稱爲工廠方法(FactoryMethod)或者方便方法(Conveniencemethod)。工廠方法的稱謂明顯和通常意義上的工廠方法不一樣,從本質上來講,類方法能夠獨立於對象而執行,因此在其餘的語言裏面類方法有的時候被稱爲靜態方法。就像@interface曾經給咱們帶來的混亂同樣,如今咱們就不去追究和爭論工廠方法的問題了,咱們看到Objective-C的文章說工廠方法,就把它看成類方法好了。
在Objective-C裏面,最受你們歡迎的類方法應該是alloc,咱們須要使用alloc來爲咱們的對象分配內存。能夠想象,若是沒有alloc,咱們將要如何來爲咱們的類分配內存!
和其餘的語言相似,下面是類方法的一些規則,請你們務必記住。
1,類方法能夠調用類方法。
2,類方法不能夠調用實例方法,可是類方法能夠經過建立對象來訪問實例方法。
3,類方法不能夠使用實例變量。類方法能夠使用self,由於self不是實例變量。
4,類方法做爲消息,能夠被髮送到類或者對象裏面去(實際上,就是能夠經過類或者對象調用類方法的意思)。
若是你們觀察一下Cocoa的類庫,會發現類方法被大量的應用於方便的對象建立和操做對象的,考慮到類方法的上述的特性,同窗們在設計本身的類的時候,爲了謀求這種方便,能夠考慮使用類方法來建立或者操做對象。筆者認爲,這個就是類方法的潛規則,在本章的範例程序裏面,筆者將要遵照這個潛規則。
在上一章咱們講了一下實例變量的做用域,實例變量的做用域的方式和其餘面向對象的語言沒有什麼不一樣。對於方法,很是遺憾的是,Objective-C並無爲咱們提供諸如public,private和protected這樣的限定,這就意味着在Objective-C裏面,從理論上來講全部的方法都是公有的。可是,咱們能夠利用Objective-C的語言的特性,咱們本身來實現方法的私有化。固然咱們本身的私有化手段沒有獲得任何的編譯器的支持,只是告訴使用者:「這是一個私有的方法,請不要使用這個方法」。因此,不管做爲類的設計者和使用者都應該清楚在Objective-C裏面的方法私有化的全部手段,這樣就在類的設計者和使用者之間達成了一種默契,這種方式明顯不是Objective-C語法所硬性規定的,因此也能夠把這種手法成爲一種潛規則。
- 關於潛規則常常看英文文檔的同窗,應該能夠遇到這樣一個詞,defactostandard,也就是筆者所說的潛規則。
本章所述的方法的私有化是一種有缺陷的手段,有必定的風險並且也沒有徹底實現私有化,在後面的章節裏面筆者會陸續的給出其餘的實現方法私有化的方法。
另外,Objective-C裏面有一個其餘不支持指針的語言沒有的一個動態特性,那就是程序在執行的時候,能夠動態的替換類的手段。動態的方法替換有不少種應用,本章實現了一個相似java裏面的final函數。和final函數不一樣的是,若是子類重寫了這個方法,編譯器不會報錯,可是執行的時候老是執行的你的超類的方法。
類方法,方法私有化和動態方法替換將是本章的主題。
8.1,本章程序的執行結果
在本章裏面,咱們將要繼續使用咱們在第4章已經構築好的類Cattle和Bull。
筆者在這裏暫時違反一下不修改已經生效的代碼規則改寫了一下Cattle和Bull類,在裏面追加了一些類方法,用於建立Cattle系列的對象。
筆者也改寫了Cattle的頭文件用來實現方法的私有化。
面向對象的程序有一個很大的特點就是動態性,可是因爲某種緣由咱們在設計超類的時候,也許會考慮把某個方法設定成爲靜態的,這樣就有了諸如final的概念。在本章咱們將要使用動態的方法替換來實現這個功能。咱們將要構築一個新類,名字叫作UnknownBull,咱們使用動態方法替換致使即便UnknownBull重載了Cattle類的saySomething,可是向UnknownBull發送saySomething的時候,仍然執行的是Cattle的saySomething。本章程序的執行結果請參照下圖:
- 圖8-1,本章程序的執行結果。
- 本章程序能夠點擊這裏下載。
8.2,實現步驟
- 第一步,按照咱們在第2章所述的方法,新建一個項目,項目的名字叫作07-InitWithAndIvarScope。若是你是第一次看本篇文章,請到這裏參看第二章的內容。
- 第二步,按照咱們在第4章的4.2節的第二,三,四步所述的方法,把在第4章已經使用過的「Cattle.h」,「Cattle.m」,「Bull.h」還有「Bull.m」,導入本章的項目裏面。
- 第三步,打開「Cattle.h」和「Cattle.m」,分別修改爲爲下面的代碼而且保存:
- #import<Foundation/Foundation.h>
@interfaceCattle:NSObject{
intlegsCount;
}
-(void)saySomething;
+(id)cattleWithLegsCountVersionA:(int)count;
+(id)cattleWithLegsCountVersionB:(int)count;
+(id)cattleWithLegsCountVersionC:(int)count;
+(id)cattleWithLegsCountVersionD:(int)count;
@end
- #import"Cattle.h"
#import<objc/objc-class.h>
@implementationCattle
-(void)saySomething
{
NSLog(@"Hello,Iamacattle,Ihave%dlegs.",legsCount);
}
-(void)setLegsCount:(int)count
{
legsCount=count;
}
+(id)cattleWithLegsCountVersionA:(int)count
{
idret=[[Cattlealloc]init];
//NEVERDOLIKEBELOW
//legsCount=count;
[retsetLegsCount:count];
return[retautorelease];
}
+(id)cattleWithLegsCountVersionB:(int)count
{
idret=[[[Cattlealloc]init]autorelease];
[retsetLegsCount:count];
returnret;
}
+(id)cattleWithLegsCountVersionC:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
return[retautorelease];
}
+(id)cattleWithLegsCountVersionD:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
if([selfclass]==[Cattleclass])
return[retautorelease];
SELsayName=@selector(saySomething);
MethodunknownSubClassSaySomething=class_getInstanceMethod([selfclass],sayName);
//ChangethesubclassmethodisRUDE!
MethodcattleSaySomething=class_getInstanceMethod([Cattleclass],sayName);
//method_impisdeprecatedsince10.5
unknownSubClassSaySomething->method_imp=cattleSaySomething->method_imp;
return[retautorelease];
}
@end
- 第四步,打開「Bull.h」和「Bull.m」,分別修改爲爲下面的代碼而且保存:
- #import<Foundation/Foundation.h>
#import"Cattle.h"
@interfaceBull:Cattle{
NSString*skinColor;
}
-(void)saySomething;
-(NSString*)getSkinColor;
-(void)setSkinColor:(NSString*)color;
+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor;
@end
- #import"Bull.h"
@implementationBull
-(void)saySomething
{
NSLog(@"Hello,Iama%@bull,Ihave%dlegs.",[selfgetSkinColor],legsCount);
}
-(NSString*)getSkinColor
{
returnskinColor;
}
-(void)setSkinColor:(NSString*)color
{
skinColor=color;
}
+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor
{
idret=[selfcattleWithLegsCountVersionC:count];
[retsetSkinColor:theColor];
//DONOTUSEautoreleasehere!
returnret;
}
@end
- 第五步,建立一個新類,名字叫作「UnknownBull」,而後分別打開「UnknownBull.h」和「UnknownBull.m」,分別修改爲爲下面的代碼而且保存:
- #import<Foundation/Foundation.h>
#import"Bull.h"
@interfaceUnknownBull:Bull{
}
-(void)saySomething;
@end
- #import"UnknownBull.h"
@implementationUnknownBull
-(void)saySomething
{
NSLog(@"Hello,Iamanunknownbull.");
}
@end
- 第六步,打開「08-Class_Method_And_Private_Method.m」,修改爲爲下面的樣子而且保存
- #import<Foundation/Foundation.h>
#import"Cattle.h"
#import"Bull.h"
#import"UnknownBull.h"
intmain(intargc,constchar*argv[]){
NSAutoreleasePool*pool=[[NSAutoreleasePoolalloc]init];
idcattle[5];
cattle[0]=[CattlecattleWithLegsCountVersionA:4];
cattle[1]=[BullcattleWithLegsCountVersionB:4];
cattle[2]=[BullcattleWithLegsCountVersionC:4];
cattle[3]=[BullbullWithLegsCount:4bullSkinColor:@"red"];
cattle[4]=[UnknownBullcattleWithLegsCountVersionD:4];
for(inti=0;i<5;i++)
{
[cattle[i]saySomething];
}
[pooldrain];
return0;
}
- 第七步,選擇屏幕上方菜單裏面的「Run」,而後選擇「Console」,打開了Console對話框以後,選擇對話框上部中央的「BuildandGo」,若是不出什麼意外的話,那麼應該出現入圖8-1所示的結果。若是出現了什麼意外致使錯誤的話,那麼請仔細檢查一下你的代碼。若是通過仔細檢查發現仍是不能執行的話,能夠到這裏下載筆者爲同窗們準備的代碼。若是筆者的代碼仍是不能執行的話,請告知筆者。
8.2,方法的私有化
- 在講述方法私有化以前,咱們首先要提到一個Objective-C裏面的一個概念,動態類型和靜態類型。
- 所謂的動態類型,就是使用id來定義一個對象,好比說
- idcattle=[[Cattlealloc]init];
- 所謂的靜態類型,就是使用已知變量的的類型來定義對象,好比說
- Cattlecattle=[[Cattlealloc]init];
- 動態類型和靜態類型各有好處,動態類型實現了多態性,使用靜態類型的時候編譯器會爲你檢查一下也許會出現危險的地方,好比說向一個靜態類型的對象發送一個它沒有定義的消息等等。
- 好的,咱們如今打開「cattle.h」,你們能夠發現,和之前的版本相比,咱們的「cattle.h」少了一個方法的定義,那就是-(void)setLegsCount:(int)count;。筆者在本章的範例程序裏面實現私有方法的手段比較簡單,直接把-(void)setLegsCount:(int)count從「cattle.h」給刪除掉了。
- 你們打開「「cattle.m」,能夠看到裏面-(void)setLegsCount:(int)count是有實現部分的。實現部分和過去的版本沒有任何區別的。
- 咱們本章裏面講述的實現方法私有化的手段,就是從頭文件當中不寫方法的聲明。這樣作會致使以下幾個現象
- 1,在類的實現文件.m裏面,你能夠向日常同樣使用[selfsetLegsCount:4]來發送消息,可是確省設定的編譯器會很不禮貌的給你一個警告。
- 2,你能夠向Cattle以及從Cattle繼承的類的靜態對象發送setLegsCount:4的消息,可是一樣,確省設定的編譯器會很不禮貌的給你一個警告。
- 3,你能夠向Cattle以及從Cattle繼承的類的動態對象發送setLegsCount:4的消息,編譯器不會向你發送任何警告的。
- 說到這裏,同窗們也許會以爲這一節的方法私有化有一點奇怪,由於在上面的第二條裏面,不能阻止對對象的私有方法進行調用。令咱們更爲惱火的是,竟然在咱們本身的類的實現文件裏面須要調用的時候產生諸如第一條的警告!
- 讓咱們冷靜一下。
- 咱們說,在面向對象的程序裏面,通常而言類的使用者只關心接口,不關心實現的。當咱們類的實現部分的某個方法,在頭文件裏面沒有定義的話,那麼因爲咱們的類的使用者只是看頭文件,因此他不該該是用咱們定義的所謂的私有方法的。這一點,對於其餘的語言來講也是同樣的,其餘的語言的私有方法和變量,若是咱們把它們改成public,或者咱們不修改頭文件,使用指針也能夠強行的訪問到私有的變量和方法的,從這個角度上來講,私有化的方法和變量也只不過是一個擺設而已,沒有人能夠阻止咱們去訪問他們,探求埋藏在裏面的奧祕。所謂的私有化只不過是一個潛規則而已,在正常的時候,咱們你們都會遵照這個潛規則的。可是被逼無奈走投無路的時候咱們也許會除了訪問私有的東西無可選擇。可是也不能過度,咱們顯然不能夠把訪問私有變量和函數看成一種樂趣。
- 說到這裏,我想你們應該能夠理解這種私有化方法的定義了。它只不過是一種信號,告訴類的使用者,「這是一個私有的函數,請不要使用它,不然後果自負」。咱們在看到別人的代碼的時候看到了這種寫法的時候,或者別人看到咱們的代碼的時候,你們都須要作到相互理解對方的隱藏私有部分的意圖。仍是仍是這句話,在大多數時候,請不要破壞潛規則。
8.3,類方法
- 咱們如今轉到本章最重要的主題,類方法。咱們將要首先關注一下類方法的聲明,如今請同窗們打開"Cattle.h"文件,能夠發現下面的代碼:
- 1+(id)cattleWithLegsCountVersionA:(int)count;
2+(id)cattleWithLegsCountVersionB:(int)count;
3+(id)cattleWithLegsCountVersionC:(int)count;
4+(id)cattleWithLegsCountVersionD:(int)count;
- 類方法和實例方法在聲明上的惟一的區別就是,以加號+爲開始,其他的部分是徹底一致的。筆者在這裏定義了4個不一樣版本的類方法,從功能上來講都是用來返回Cattle類或者其子類的對象的,其中cattleWithLegsCountVersionA到C是咱們這一節講解的重點。
- 讓咱們首先打開「Cattle.m」,關注一下下面的代碼:
- 1+(id)cattleWithLegsCountVersionA:(int)count
2{
3idret=[[Cattlealloc]init];
4//NEVERDOLIKEBELOW
5//legsCount=count;
6[retsetLegsCount:count];
7return[retautorelease];
8}
9+(id)cattleWithLegsCountVersionB:(int)count
10{
11idret=[[[Cattlealloc]init]autorelease];
12[retsetLegsCount:count];
13returnret;
14}
- 咱們須要使用類方法建立對象,因此在第3行,咱們使用了咱們比較熟悉的對象的建立的方法建立了一個對象。你們注意一下第5行,因爲類方法是和對象是脫離的因此咱們是沒法在類方法裏面使用實例變量的。第6行,因爲咱們建立了對象ret,因此咱們能夠向ret發送setLegsCount:這個消息,咱們經過這個消息,設定了Cattle的legsCount實例變量。在第7行,咱們遇到了一個新的朋友,autorelease。咱們在類方法裏面建立了一個對象,當咱們返回了這個對象以後,類方法也隨之結束,類方法結束就意味着在咱們寫的類方法裏面,咱們失去了對這個對象的參照,也就永遠沒法在類方法裏面控制這個對象了。在Objective-C裏面有一個規則,就是誰建立的對象,那麼誰就有負責管理這個對象的責任,類方法結束以後,除非和類的使用者商量好了讓類的使用者釋放內存,不然咱們沒法直接的控制這個過程。
- 記憶力好的同窗應該能夠回憶起來,筆者曾經在第二章提到過一種延遲釋放內存的技術,這個就是autorelease。關於autorelease以及其餘的內存管理方法,咱們將在下一章放到一塊兒講解。到這裏你們記住,使用類方法的潛規則是你要使用類方法操做對象,當你須要使用類方法建立一個對象的時候,那麼請在類方法裏面加上autorelease。
- 咱們來看看cattleWithLegsCountVersionB的實現部分的代碼,和cattleWithLegsCountVersionA惟一區別就是咱們在建立的時候就直接的加上了autorelease。這樣符合建立對象的時候「一口氣」的把全部須要的方法都寫到一塊兒的習慣,採起什麼方式取決於我的喜愛。
- 咱們再打開「08-Class_Method_And_Private_Method.m」,參看下面的代碼
- 1cattle[0]=[CattlecattleWithLegsCountVersionA:4];
2cattle[1]=[BullcattleWithLegsCountVersionB:4];
- 咱們在回頭看看本章程序的執行結果,心細的同窗也許發現了一個很嚴重的問題,咱們在第2行代碼裏面想要返回一個Bull的對象,可是輸出的時候卻變成了Cattle,緣由就是咱們在cattleWithLegsCountVersionB裏面建立對象的時候,使用了idret=[[[Cattlealloc]init]autorelease]。因爲Bull裏面沒有重寫cattleWithLegsCountVersionB,因此除非咱們重寫cattleWithLegsCountVersionB不然咱們向Bull發送cattleWithLegsCountVersionB這個類方法的時候,只能獲得一個Cattle的對象。咱們能夠要求咱們的子類的設計者在他們的子類當中重寫cattleWithLegsCountVersionB,可是這樣明顯很是笨拙,失去了動態的特性。咱們固然有辦法解決這個問題,如今請你們回到「Cattle.m」,參照下列代碼:
- 1+(id)cattleWithLegsCountVersionC:(int)count
2{
3idret=[[selfalloc]init];
4[retsetLegsCount:count];
5return[retautorelease];
6}
- 咱們的解決方案就在第3行,咱們不是用靜態的Cattle,而是使用self。說到這裏也許你們有些糊塗了,在其餘的語言當中和self比較相似的是this指針,可是在Objective-C裏面self和this有些不大同樣,在類函數裏面的self實際上就是這個類自己。你們能夠打開debugger觀察一下,self的地址就是Bull的Class的地址。因此程序執行到上面的代碼的第3行的時候,實際上就等同於idret=[[[Bullclass]alloc]init];
- 咱們能夠在類方法裏面使用self,咱們能否經過使用self->legsCount來訪問實例變量呢?答案是不能夠,由於在這個時候對象沒有被建立也就是說,沒有爲legsCount分配內存,因此沒法訪問legsCount。
- 因爲Bull類在程序被調入內存的時候就已經初始化好了,Bull類裏面的實例函數應該被放到了代碼段,因此從理論上來講,咱們能夠經過使用[selfsetLegsCount:count]來調用實例方法的,可是不幸的是Objective-C沒有容許咱們這樣作,咱們在類方法中使用self來做爲消息的接收者的時候,消息老是被翻譯成爲類方法,若是發送實例方法的消息的話,會在執行的時候找不到從而產生異常。這樣作是有必定的道理的,由於通常而言,實例方法裏面不免要使用實例變量,在類方法當中容許使用實例方法,實際上也就容許使用實例變量。
- 關於self你們須要記住下面的規則:
- 1,實例方法裏面的self,是對象的首地址。
- 2,類方法裏面的self,是Class.
- 儘管在同一個類裏面的使用self,可是self卻有着不一樣的解讀。在類方法裏面的self,能夠翻譯成classself;在實例方法裏面的self,應該被翻譯成爲objectself。在類方法裏面的self和實例方法裏面的self有着本質上的不一樣,儘管他們的名字都叫self。
- 請同窗們再次回到圖8-1,能夠發現經過使用神奇的self,咱們動態的建立了Bull類的對象。可是等一下,咱們的程序並不完美,由於Bull類的skinColor並無獲得初始化,因此致使了null的出現。咱們在設計Cattle類也就是Bull的超類的時候,明顯咱們沒法預測到Bull類的特徵。消除這種問題,咱們能夠在獲得了Bull對象以後使用setSkinColor:來設定顏色,固然咱們也能夠直接寫一個Bull類的方法,來封裝這個操做,請同窗們打開「Bull.h」:
- +(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor;
- 咱們追加了一個類方法,bullWithLegsCount:bullSkinColor:用於建立Bull對象,請同窗們打開「Bull.m」:
- 1+(id)bullWithLegsCount:(int)countbullSkinColor:(NSString*)theColor
2{
3idret=[selfcattleWithLegsCountVersionC:count];
4[retsetSkinColor:theColor];
5//DONOTUSEautoreleasehere!
6returnret;
7}
- 上面這一段代碼相信你們均可以看明白,筆者就不在這裏贅述了。可是筆者須要強調一點,在這裏咱們不須要調用autorelease的,由於咱們沒有在這裏建立任何對象。
- 通過了這個改造,經過在「08-Class_Method_And_Private_Method.m」裏面咱們使用
- cattle[3]=[BullbullWithLegsCount:4bullSkinColor:@"red"];
- 使得咱們的代碼終於正常了,請參照圖8-1的第4行輸出。
8.4,使用動態方法替換實現final功能
- 首先請同窗們打開「Cattle.m」,參照下面的代碼片段:
- +(id)cattleWithLegsCountVersionD:(int)count
{
idret=[[selfalloc]init];
[retsetLegsCount:count];
if([selfclass]==[Cattleclass])
return[retautorelease];
SELsayName=@selector(saySomething);
MethodunknownSubClassSaySomething=class_getInstanceMethod([selfclass],sayName);
//ChangethesubclassmethodisRUDE!
MethodcattleSaySomething=class_getInstanceMethod([Cattleclass],sayName);
//method_impisdeprecatedsince10.5
unknownSubClassSaySomething->method_imp=cattleSaySomething->method_imp;
return[retautorelease];
}
@end
- 在cattleWithLegsCountVersionD裏面,咱們將要經過使用動態的方法替換技術來實現final方法。
第3,4行代碼,是用於建立Cattle或者從Cattle類繼承的對象,而且設定實例變量legsCount。
第6,7行代碼,是用來判斷調用這個類方法的self是否是cattle,若是是cattle的話,那麼就直接返回,由於咱們要在這個方法裏面把子類的saySomething替換成爲Cattle的saySomething,若是類是Cattle的話,那麼很明顯,咱們不須要作什麼事情的。
第9行代碼是老朋友了,咱們須要獲得方法的SEL。
- 第10行和第12行,咱們須要經過Objective-C的一個底層函數,class_getInstanceMethod來取得方法的數據結構Method。讓咱們把鼠標移動到Method關鍵字上面,點擊鼠標右鍵盤,選擇「Jumptodefinition」,咱們能夠看到在文件「objc-class.h」裏面的Method的定義。Method其實是類方法在Class裏面的數據結構,系統會使用Method的信息來構築Class的信息。在Method類型的聲明裏面,咱們看到了下面的代碼
- typedefstructobjc_method*Method;
structobjc_method{
SELmethod_name;
char*method_types;
IMPmethod_imp;
};
- 其中SEL和IMP咱們已經很熟悉了,method_types是方法的類型信息,Objective-C使用一些預約義的宏來表示方法的類型,而後把這些信息放到method_types裏面。
- 須要強調的是,蘋果在10.5以後就降級了不少Objective-C底層的函數,而且在64位的應用當中使得這些函數失效,筆者對剝奪了衆多程序員的自由而感到遺憾。
- 第14行的代碼,咱們把子類的函數指針的地址替換成爲Cattle類的saySomething,這樣不管子類是否重寫saySomething,執行的時候因爲runtime須要找到方法的入口地址,可是這個地址老是被咱們替換爲Cattle的saySomething,因此子類經過cattleWithLegsCountVersionD取得對象以後,老是調用的Cattle的saySomething,也就實現了final。固然,這種方法有些粗魯,咱們強行的不顧後果的替換了子類的重寫。
- 重要本節提到的final的實現方法,沒有任何蘋果官方的文檔建議這樣作,純屬筆者自創僅供你們參考,若是使用風險自擔。
- 替換的結果,就是雖然咱們在「08-Class_Method_And_Private_Method.m」裏面的cattle[4]l裏面使用UnknownBull是圖返回UnknownBull對象,咱們也確實獲得了UnknownBull對象,可是不一樣的是,咱們在cattleWithLegsCountVersionD裏面狸貓換太子,把UnknownBull的saySomething變成了Cattle的saySomething。
- 讓咱們回到圖8-1,咱們發現最後一行的輸出爲Cattle的saySomething。
- 關於final的實現方式,咱們固然能夠使用一個文明的方法來告知子類的使用者,咱們不想讓某個方法被重寫。咱們只須要定義一個宏
- #defineFINAL
- 類的使用者看到這個FINAL以後,筆者相信在絕大多數時候,他會很配合你不會重寫帶FINAL定義的方法的