第一條 Object-C語言的起源
- oc是面向?qū)ο笳Z言
- 雖然oc是面向?qū)ο蟮拇ビ祝菍Ρ萰ava c++這類面向?qū)ο蟮恼Z言還是略有差別:
1>java c++的面向?qū)ο笳Z言使用的是“函數(shù)調(diào)用”
2>而oc的面向?qū)ο笳Z言使用的是“消息結(jié)構(gòu)” - oc的消息型語言是從Smalltalk演化而來的柜蜈,Smalltalk是消息型語言的鼻祖
- 消息結(jié)構(gòu)和函數(shù)調(diào)用在代碼上的區(qū)別:
//這是oc的消息結(jié)構(gòu)
NSObject *obj = [[NSObject alloc] init];
[obj perform:parm1 parm:parm2];
//這是c++的函數(shù)調(diào)用
Object *obg = new Object;
obj->perform(parm1, parm2);
上述的就是寫的語法上的區(qū)別豆同,一個是通過:锦爵,:來的辐脖,另一個是直接最后括號里作為參數(shù)傳過去的 - 消息結(jié)構(gòu)的語言:其運行時執(zhí)行的代碼由運行環(huán)境來決定(正是這個特性才有我們平時說的runtime)
- 函數(shù)調(diào)用的語言:其運行時執(zhí)行的代碼由編譯器決定(在編譯時期就決定了執(zhí)行的代碼。)
- 總結(jié)5僵娃,6概作,oc在編譯時期并不決定時期執(zhí)行的代碼,比如歌函數(shù)只有聲明默怨,沒有具體實現(xiàn)在oc中就可以變易通過讯榕,只有在運行時如果運行環(huán)境發(fā)現(xiàn)沒有具體實現(xiàn)的時候才會報錯。而函數(shù)調(diào)用語言如果只聲明沒有具體實現(xiàn)匙睹,在編譯時期就會報錯愚屁。
- 多態(tài):疑問,如果4中的c++代碼痕檬,是個多態(tài)的話怎么辦集绰,不是說c++這類語言是在編譯的時候就要決定調(diào)用那個實現(xiàn)嗎,這個多態(tài)怎么辦谆棺,答案是c++這類代碼,如果是多態(tài)的話,比較特殊改淑,也是在運行時起決定的碍岔,在運行時期通過“虛方法表”來查出到底該執(zhí)行那個函數(shù)的實現(xiàn)。然而對于oc這類消息型語言朵夏,不管你是不是多態(tài)我都是在運行時期才去查找要執(zhí)行的方法蔼啦,這就是為什么咱們7中所說的如果只聲明不實現(xiàn)在編譯時期不會報錯的原因
- Object-C是為c語言添加了面向?qū)ο筇匦缘腸語言的超集的語言
第二條 在類的頭文件中盡量少引入其他頭文件
-
什么時候使用#import什么時候@class(向前聲明)
image.png
如上圖截圖所示,我在person類中的頭文件需要加入一個employer的類的屬性:這時候在編譯的時候仰猖,我們只需要知道有一個類名叫做YTEmployer就行了捏肢,不需要知道YTEmployer里面的具體實現(xiàn)細節(jié),所以上面的#import "YTEmployer.h"就顯得又點引入的太多了饥侵,所以優(yōu)雅的做法應該是只聲明一下該類就行了鸵赫,即使用@class "YTEmployer.h"就行了,這種使用@class的行為叫做“向前聲明”躏升。
ps:所以為了提高編譯時的效率辩棒,盡量能不在.h中使用全部引入的#import就少使用,對于具體細節(jié)的實現(xiàn)放在.m中在導入
ps:也就是將引入頭文件的時機盡量延后膨疏,只在確有需要的時候才引入 -
1中的介紹我們知道一睁,@class增加了編譯的效率,但是@class相比于#import還有一個好處佃却,請看下面如果使用#import的話:
在YTPerson中需要知道雇員的屬性者吁,導入了YTEmployer.h
image.png
在YTEmployer.h中需要有一個增加雇員的方法,導入了YTPerson.h
image.png
如上述這樣怎么辦饲帅,這個時候你去編譯的時候复凳,如果編譯YTPerson需要引入YTEmployer.h,如果編譯YTEmployer.h需要引入YTPerson洒闸,所以假如我們使用了上述的#import分別進行導入染坯,這樣在解析其中一個頭文件的時候編譯器發(fā)現(xiàn)他引入了另一個頭文件,然后去編譯另一個發(fā)現(xiàn)另一個也引入這個頭文件丘逸,這樣雖不像使用#include能造成死循環(huán)单鹿,但是這樣的使用會使這兩個文件永遠都有一個無法正確編譯,通過上面的截圖你也能看出來 深纲,總有一個會報錯仲锄。
so,此時我們就使用@class就會避免這種循環(huán)引用的問題,如下:
image.png
image.png
這樣就沒問題湃鹊。你看到截圖中兩個我都用了@class儒喊。其實如果僅僅是為了編譯能通過,其中一個使用@class就行了币呵,就不會循環(huán)引用了怀愧,但是學習了1侨颈,我們要盡量延后引入,所以兩個都應該使用@class
-
學習了1芯义,2我們知道在.h中盡量使用@class哈垢,而不是@import,但是有沒有在.h中必須要要引入頭文件的呢扛拨?答案是:有耘分,例如:
1》情況1:自定義的這個類繼承自父類,.h中必須引入绑警,這個毋庸置疑(YTSonPerson 繼承自YTPerson)
image.png
2》情況2:如果我們這個類要遵從某個協(xié)議求泰,也必須使用#import,使用@class只能知道說有這個名字的協(xié)議存在计盒,但是卻不知道這個協(xié)議里面有什么方法渴频,但是我們的編譯器在此時是需要知道這個協(xié)議有什么方法的:
YTEmployer中定義了協(xié)議:
image.png
YTPerson中要遵從這個協(xié)議:
image.png
所以此時我們就也必須引入頭文件使用#import。
但是對于情況2章郁,我們也就是只是必須知道YTEmployer協(xié)議中的方法枉氮,對于YTEmployer其他全局具體內(nèi)容也是不必知道的,我們就使用#import顯的也不是十分優(yōu)雅暖庄,所以“我們應該把協(xié)議單獨放到一個文件中來寫”:
總結(jié):
1》除非有必要聊替,否則不要引入頭文件。一般來說培廓,應在某個類的頭文件中使用向前聲明來提及別的類惹悄,并在實現(xiàn)文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合
2》有時無法使用向前聲明肩钠,比如要聲明某個類遵循一項協(xié)議泣港。這種情況下,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中价匠。如果不行的話当纱,就把協(xié)議單獨放在一個頭文件中,然后將其引入
第三條 多用字面量語法踩窖,少用與之等價的方法
1. NSString 字符串字面量語法
2. 字面量數(shù)值
3. 字面量數(shù)組
id obj1, obj2, obj3;
NSArray *arrayA = [NSArray arrayWithObjects:obj1,obj2,obj3, nil];
NSArray *arrayB = @[obj1, obj2, obj3];
問題:假如obj2是nil坡氯,來比較兩種方式的區(qū)別
1. 首先兩種方式都會報錯崩潰
2. arrayA這中方式,數(shù)組arrayA已經(jīng)被創(chuàng)建出來了洋腮,并且obj1已經(jīng)被添加進去了箫柳,到了obj2發(fā)現(xiàn)obj2是nil,就會報錯啥供。
3. arrayB這種方式悯恍,他的效果相當于先創(chuàng)建數(shù)組,然后再把中括號里面的所有對象都加到這個數(shù)組中伙狐,他這種方式在創(chuàng)建數(shù)組的時候就會檢查是否有nil涮毫,那么檢查到obj2為nil瞬欧,直接這個數(shù)組就不創(chuàng)建了
4. 綜上,還是字面量數(shù)組方式比較好一點窒百,因為數(shù)組中有nil本身就是一個bug黍判,一個錯誤,字面量可以在未創(chuàng)建出數(shù)組之前就判斷有nil篙梢。報錯,比arrayA的方式要好榴啸。比正常方式更加安全
4.字面量字典
同樣也會有值為nil的問題劝堪,這個道理和字面量數(shù)組是一模一樣的
字面量語法的局限性
1.上面我們說的這些類都可以使用字面量語法逼友,但是如果我們自定義這些類的“子類”,這些“子類”不能使用字面量語法,盡管使用了也會正常運行編譯妄呕,但是回報警告
“Incompatible pointer types initializing 'SonNSString *' with an expression of type 'NSString *'”
“Incompatible pointer types initializing 'SonArray *' with an expression of type 'NSArray *'”
所以不建議我們對這些類的子類使用字面量語法
2.使用字面量語法創(chuàng)建出來的都是不可變的--"Incompatible pointer types initializing 'NSMutableArray *' with an expression of type 'NSArray *'"
如果想用字面量語法創(chuàng)建出可變的
總結(jié)
- 應該使用“字面量”語法來創(chuàng)建字符串,數(shù)值嗽测,數(shù)組绪励,字典。與創(chuàng)建此類對象的常規(guī)方法相比唠粥,這么做更加簡明扼要
- 應該通過取下標操作來訪問數(shù)組下標或字典中的鍵對應的元素
- 用字面量語法創(chuàng)建數(shù)組或字典時疏魏,若值中有nil,則會拋出異常晤愧。因此無比確保值里不含nil大莫。(即使是常規(guī)方法有nil也會拋出異常)
因此使用字面量語法1,簡明扼要 2官份,對于有nil值只厘,數(shù)組和字典來說字面量語法更安全。所以建議“多用字面量語法舅巷,少用與之等價的方法”
第四條 多用類型常量羔味,少用#define預處理指令
Object-C定義常量的三種方式
- 第一種
#define Animation_Const 0.6
- 第二種
static const NSInteger kAnimation_Const = 0.3;
- 第三種
在.h 中
extern NSString *const Animation_Notification;
在.m中
NSString *const Animation_Notification = @"value";
1這種方式,“不建議使用”钠右,這樣定義出來的常量不包含類型信息赋元,可讀性不強,編譯器針對這種方式做的就是在編譯之前把引用到的“ Animation_Const”這個東西替換為未0.6爬舰,其他的不會做人和操作们陆。這樣的話如果中途有人改了這個常量值,編譯器也不會警告情屹,將導致程序中不同的地方常量的值不一樣
2這種方式坪仇,比較好,但是僅僅使用與定義在.m中垃你,這種方式如果僅僅是在自己的編譯單元(一般都是.m是自己的一個獨立編譯單元)中使用的話用這個比較好
包含類型信息椅文,清楚的描述了常量的含義喂很。并且我們使用了static和const常量,因此中途如果有人試圖修改該常量編譯器就會報錯皆刺,因此相比較于1更加安全
3這種方式少辣,剛才我們說2的好處,但是2用在編譯單元中也就是.m中的時候比較好羡蛾,但是有些時候我就是需要外部能夠訪問到我的這個常量怎么辦漓帅,就是用3種方式,在.h中聲明痴怨,在.m中定義忙干,這樣一舉兩得。
---使用extern關(guān)鍵字來聲明的全局常量浪藻,會出現(xiàn)在全局符號表中
----編譯器再看到extern關(guān)鍵字的時候捐迫,編譯器就能明白如何在引入此頭文件的代碼中處理該常量了,這個extern關(guān)鍵字就是告訴編譯器爱葵,在全局符號表中將會有一個名叫Animation_Notification的符號施戴,也就是說編譯器無需查看其定于你,就允許代碼使用此常量萌丈,因為他知道赞哗,當連接成二進制文件之后,肯定能找到這個常量
第五條 用枚舉表示狀態(tài)浓瞪、選項懈玻、狀態(tài)碼
1.第一種定義枚舉:
這種方式下面定義枚舉變量的時候需要:
enum SocketConnectState state = SocketConnectStateDisConnected;
這樣每次都需要使用那個enum,不太簡潔乾颁,怎么才能不每次都適用enum呢涂乌,看第二種方式。
2.第二種定義枚舉
這種方式下面定義枚舉變量的時候就只需要:
ConnectState state = SocketConnectStateDisConnected;
| 上面這兩種方式都沒有定義枚舉的“類型”英岭,編譯器會為枚舉分配一個獨有的編號湾盒,從0開始,每個枚舉遞增1诅妹,但是這個枚舉具體類型取決于編譯器罚勾,不過其“二進制位”的個數(shù)必須能完全表示下枚舉編號才行,前例中因為只有三個枚舉吭狡,所以最大也就是2尖殃,即0x10就能夠表示,所以此枚舉類型位char就可以了划煮,如果枚舉種類更多了送丰,那么就可能不是char了,可能就是int或者什么的了
3-1. 第三種-1定義枚舉
前面1弛秋,2兩種器躏,都沒有聲明枚舉類型俐载,第三種直接聲明了枚舉的數(shù)據(jù)類型。并且還可以設(shè)置枚舉的起始值登失。
3-2. 第三種-2定義枚舉
如果定義的這些選項可以彼此“組合”遏佣,就更應該使用這種方式了。各選項之間可以通過“按位或操作符”來組合揽浙,請看:
因此
EnumNumber num = EnumTwo | EnumThree;
就相當于0 0 0 0 1 1 因為進行了或操作嘛状婶,
因此在下面判斷的時候,就是只有這個枚舉值得位一樣就可以進入馅巷,所以:
這種3-2這種枚舉定義方式系統(tǒng)庫在非常頻繁的使用
4.第四種 定義枚舉
使用的這種“NS_ENUM”太抓,“NS_OPTIONS”系統(tǒng)宏,這些宏具備向后兼容能力令杈,如果目標平臺的編譯器支持新標準就是用新式語法,否則改用舊式語法
NS_ENUM宏如果是新式語法碴倾,上面的定義就相當于:
NS_OPTIONS宏針對看是否支持c++編譯
1》如果不按c++編譯逗噩,上面的枚舉展開其實和NS_ENUM的展開是一樣的,即:
為什么呢?--原因在于跌榔,用“按位或運算”來操作兩個枚舉值時异雁,c++編譯模式的處理辦法與非c++模式不一樣的。用“或”運算操作兩個枚舉值時僧须,c++認為運算結(jié)果的數(shù)據(jù)類型應該是枚舉的底層數(shù)據(jù)類型纲刀,也就是NSUInteger,并且c++還不允許將這個底層類型“隱式轉(zhuǎn)換”為枚舉類型本身,針對上面的展開担平,加入我們這樣來使用:
EnumNumber num = EnumFour | EnumSenven;
若編譯器是按c++模式編譯的(也可能是按object-c++模式編譯)示绊,那么上面的這句代碼就會報錯"不可能初始化這個變量類型"
所以我們?nèi)绻胱円咨厦孢@句代碼,就要將“按位或操作”的結(jié)果進行“顯示轉(zhuǎn)換”(因為c++編譯不能隱式轉(zhuǎn)換)為枚舉類型(EnumNumber類型)暂论。
總結(jié)面褐,因為我們不想每次都進行一行顯示類型轉(zhuǎn)換,所以我們應該用NS_OPTIONS宏取胎。使用NS_OPTIONS就是擔心我們?nèi)绻凑誧++模式編譯了展哭,如果還沒有顯示轉(zhuǎn)換就會報錯,如果顯示轉(zhuǎn)換每次還比較麻煩闻蛀,所以直接用了NS_OPTIONS宏
1》凡是需要按位操作的都應該使用NS_OPTIONS
2》若是枚舉不需要組合也就是不需要按位操作什么的使用NS_ENUM就可以了
5.在switch中使用枚舉
在switct如果食用了枚舉我們最好不要使用default分之匪傍,為什么呢,加入我們不適用default分之觉痛,我們switch中如果少了那個枚舉役衡,編譯就會警告,但是如果我們加了default編譯器就不會警告秧饮,所以使用枚舉在switch表示狀態(tài)機的時候最好不要加default
枚舉本節(jié)全文總結(jié)
1.應該用枚舉來表示狀態(tài)機的狀態(tài)映挂,傳遞給方法的選項以及狀態(tài)碼等值泽篮,給這些值起個易懂的名字
2.如果傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用柑船,那么就將各選項值定義為2的冪帽撑,以便通過按位或操作將其組合起來
3.用NS_ENUM 與NS_OPTIONS宏定義來定義枚舉類型,并指明其底層數(shù)據(jù)類型鞍时。這樣做可以確保枚舉是開發(fā)者所選的底層數(shù)據(jù)類型實現(xiàn)出來的亏拉,而不會采用編譯器所選的類型
4.在處理枚舉類型的switch語句中不要實現(xiàn)default分之。這樣的話逆巍,加入新枚舉之后編譯器就會提示開發(fā)者:switch語句并未處理所有枚舉及塘。