這本書(shū)中的內(nèi)容都是精華,文章也很簡(jiǎn)潔了,但是我要讓他更簡(jiǎn)潔更易懂,由于我是一個(gè)初學(xué)者,所以就用我自己能夠理解的直白話(huà)語(yǔ)總結(jié)一下這52個(gè)方法,希望再看的時(shí)候理解起來(lái)不用這么麻煩,好的,馬上開(kāi)始吧!
第一章 熟悉Objective-C
Objective-C(以下簡(jiǎn)稱(chēng)OC)是C語(yǔ)言的超集,是在C語(yǔ)言的基礎(chǔ)上添加了面向?qū)ο筇匦?/p>
1. 了解Objective-C的起源
首先,OC是一門(mén)面向?qū)ο笳Z(yǔ)言,但是語(yǔ)法和其他的面向?qū)ο笳Z(yǔ)言有很大差異,運(yùn)用大量的中括號(hào),這讓寫(xiě)習(xí)慣其他語(yǔ)言的人感覺(jué)很別扭,不過(guò),什么東西都是,習(xí)慣就好。
OC使用的是“消息結(jié)構(gòu)”(messaging structure)而不是“函數(shù)調(diào)用”(function calling)是由Smalltalk演化而來(lái),Smalltalk是消息型語(yǔ)言的鼻祖。好的,那么解釋一下什么是消息型語(yǔ)言,函數(shù)調(diào)用相信大家都理解,消息結(jié)構(gòu)語(yǔ)言,是運(yùn)行時(shí)所執(zhí)行的代碼是由運(yùn)行環(huán)境決定的,而不是由編譯器決定的,這就決定了OC是一門(mén)動(dòng)態(tài)型語(yǔ)言,也就是人們說(shuō)的運(yùn)行時(shí)(runtime),這個(gè)怎么理解呢,就是你先聲明一下你是一個(gè)類(lèi)型,但是具體是什么類(lèi)型,等真正運(yùn)行的時(shí)候在確定,好了,就到這,說(shuō)多了我也不懂。
下面說(shuō)一下OC的內(nèi)存管理,OC采用引用計(jì)數(shù)機(jī)制,就是你調(diào)用一次對(duì)象,我就把引用計(jì)數(shù)加一,銷(xiāo)毀一次就減一,當(dāng)引用計(jì)數(shù)為0的時(shí)候,系統(tǒng)就會(huì)把這個(gè)對(duì)象銷(xiāo)毀,現(xiàn)在在ARC環(huán)境下,計(jì)數(shù)是由編譯器幫我們管理的,所以我們很幸運(yùn),不用太糾結(jié)于這個(gè)地方。
C語(yǔ)言中*代表的是指針,OC中指針是用來(lái)指示對(duì)象的,所以聲明一個(gè)變量語(yǔ)法是:
NSString *aString = @"The string"
OC中對(duì)象都是分配在堆內(nèi)存上的,所以aString這個(gè)指針指向堆內(nèi)存,在堆內(nèi)存中有一個(gè)NSString對(duì)象,這也就是說(shuō),如果我們?cè)賱?chuàng)建一個(gè)變量,讓其指向同一個(gè)內(nèi)存地址:
NSString *aString = @"The string";
NSString *bString = aString;
這樣我們就創(chuàng)建了指向同一個(gè)內(nèi)存地址的兩個(gè)指針,而不是拷貝該對(duì)象,在OC中我們有時(shí)候會(huì)看到定義一個(gè)變量不帶*,這種變量有可能使用棧內(nèi)存,例如CGRect,這種變量保存的不是OC對(duì)象,而是系統(tǒng)框架的結(jié)構(gòu)體,創(chuàng)建這種結(jié)構(gòu)體比創(chuàng)建對(duì)象節(jié)省內(nèi)存開(kāi)銷(xiāo)。
2. 在頭文件中盡量少引入其他頭文件
OC編寫(xiě)類(lèi)的標(biāo)準(zhǔn)方式是文件名做為類(lèi)名,同事生成一個(gè).m文件和一個(gè).h文件,一般情況下.h文件負(fù)責(zé)對(duì)外提供接口,.m文件負(fù)責(zé)內(nèi)部實(shí)現(xiàn),當(dāng)在一個(gè)類(lèi)中需要引入其他類(lèi)的時(shí)候,我們就要通過(guò)#import導(dǎo)入其他頭文件,其實(shí)有很多時(shí)候我們?cè)?h中引入頭文件是不需要的,我們只要用@class聲明一個(gè)類(lèi)就可以了,我下面用例子說(shuō)一下這么做的好處,現(xiàn)在有三個(gè)類(lèi)
one.h
two.h
three.h
我們?cè)趖wo類(lèi)中需要one類(lèi),我們可以這樣引入
two.h
#import "one.h"
當(dāng)我們?cè)趖hree類(lèi)中需要two類(lèi)的時(shí)候我們這樣引入
three.h
#import "two.h"
那么問(wèn)題來(lái)了,這樣做我們?cè)趖hree類(lèi)中引入two類(lèi)的時(shí)候間接引入了one類(lèi),而在three類(lèi)中我們可能不需要one類(lèi),這樣我們?cè)诰幾g的時(shí)候會(huì)增加編譯時(shí)間(注意:只是增加編譯時(shí)間,和運(yùn)行時(shí)間沒(méi)有關(guān)系),雖然這樣做不會(huì)增加我們的運(yùn)行時(shí)間,但是當(dāng)我們間接引入過(guò)多頭文件的時(shí)候,編譯時(shí)間會(huì)大大增加,并且影響代碼的閱讀性,這個(gè)時(shí)候@class就很好用了,我們只要這樣寫(xiě):
two.h
@class one;
three.h
@class two;
這樣就可以避免在three類(lèi)引入two類(lèi)的時(shí)候間接引入one類(lèi)了
這里的@class只是聲明一個(gè)類(lèi),而不是把類(lèi)的所有細(xì)節(jié)引入,當(dāng)我們要用的類(lèi)的屬性、方法的時(shí)候,我們只要在.m中引入頭文件就可以了,這樣做可以提高我們的編譯效率,并且可以避免循環(huán)引用,雖然#import可以避免我們循環(huán)引用,但是在a類(lèi)引用b類(lèi)同時(shí)b類(lèi)又引用a類(lèi)的時(shí)候還是有一個(gè)類(lèi)無(wú)法被正確編譯,另外這樣做還可以降低類(lèi)之間的耦合。
有時(shí)候我們不得不引入頭文件,例如某個(gè)類(lèi)要繼承一個(gè)父類(lèi),這個(gè)時(shí)候必須引入父類(lèi)頭文件,還有我們要遵從某個(gè)協(xié)議的時(shí)候(比如代理協(xié)議),此時(shí)我們就必須要引入頭文件,來(lái)知道細(xì)節(jié)。
一般我們解決協(xié)議的引用,都是把協(xié)議放在分類(lèi)里(class-continuation category)
3. 多用字面量語(yǔ)法
字面量語(yǔ)法是一種“語(yǔ)法糖”,OC中常用的字面量:
| 字面量類(lèi)型 | 實(shí)現(xiàn)方式 |
|---|---|
| 字符串 | NSString *aString = @"string" |
| 數(shù)值 | NSNumber *aNumber = @1 |
| 數(shù)組 | NSArray *aArray = @[@"object1", @"object2", @"object3"] |
| 字典 | NSDictionary *aDictionary = @{@"key1":@"value1", @"key2":@"value2", @"key3":@"value3"} |
我們可以看到,字面量語(yǔ)法簡(jiǎn)潔易懂,字面量語(yǔ)法的另一個(gè)好處就是當(dāng)我們輸入的值為nil的時(shí)候編譯器會(huì)提示錯(cuò)誤,這樣可以確保我們得到想要的字典或數(shù)組,如果我們用方法生成數(shù)組或字典可能會(huì)有不一樣的結(jié)果,例如:
id object1 = @"string1";
id object2 = nil;
id object3 = @"string3";
NSArray *aArray = [NSArray arrayWithObjects:object1, object2, object3, nil];
這樣創(chuàng)建出來(lái)的數(shù)組實(shí)際上只有一個(gè)對(duì)象,因?yàn)閛bject2是nil,如果用字面量創(chuàng)建,會(huì)直接報(bào)錯(cuò)
字面量的局限性就是創(chuàng)建的數(shù)組和字典都是不可變的,還有除了字符串以外,創(chuàng)建的對(duì)象都必須屬于Foundation框架
4. 多用類(lèi)型常量,少用#define預(yù)處理指令
我們?cè)诙x一個(gè)常量的時(shí)候通常會(huì)用#define,例如:
#define kBottomMargin 25
首先說(shuō)一下這么做壞處,這樣定義首先沒(méi)有描述常量類(lèi)型,另外如果這樣定義在.h文件中,所有引用這個(gè).h文件的代碼都會(huì)把kBottomMargin替換為25,我們應(yīng)盡量避免這樣定義。
替代方法:
static const NSUInteger kBottomMargin = 25;
這樣定義一個(gè)常量好處是我們確定了常量類(lèi)型,并且,當(dāng)我們?cè)噲D修改這個(gè)常量的時(shí)候,編譯器會(huì)報(bào)錯(cuò),因?yàn)橛衏onst修飾。
這樣定義,我們解決了類(lèi)型的問(wèn)題,但是如果放在.h文件中定義的時(shí)候,還是無(wú)法消除引用的問(wèn)題,而實(shí)際上我們有的時(shí)候要把這種常量暴露在外面,供別人使用(例如通知名稱(chēng)),這時(shí)候我們可以用另外一種方法:
在.h文件中:
extern NSString *const kNotifictionName;
在.m中
NSString* const kNotifictionName = @"kNotifictionName";
這樣就圓滿(mǎn)解決了我們的問(wèn)題,所以我們以后可以放棄用#define定義一個(gè)常量了,同時(shí)我們也要避免在.h文件中用static const來(lái)定義一個(gè)常量
5. 用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)嗎
枚舉的好處相信大家都知道,基本的寫(xiě)法大家也都會(huì),這里提一下大家可能忽略的地方:
- 編譯器為枚舉值分配獨(dú)有的編號(hào),從0開(kāi)始,然而這個(gè)初始值我們可以自己設(shè)定,只要設(shè)定第一個(gè),后面的會(huì)自動(dòng)加1
- 我們可以自行設(shè)定保存枚舉類(lèi)型變量的“底層數(shù)據(jù)類(lèi)型”
enum buttonState: NSInteger{/* ... */};
- 系統(tǒng)為我們提供了兩種宏創(chuàng)建枚舉,分別是定義普通枚舉的宏
NS_ENUM和創(chuàng)建可選多項(xiàng)可選類(lèi)型的枚舉宏NS_OPTIONS
例子:
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
UIViewAnimationTransitionNone,
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
- switch語(yǔ)句中,若用枚舉來(lái)定義狀態(tài)機(jī),最好不要有default分支,這樣做是為了確保switch語(yǔ)句能處理所有狀態(tài)