c2c網(wǎng)站頁(yè)面設(shè)計(jì)特點(diǎn)企業(yè)網(wǎng)站seo點(diǎn)擊軟件
目錄
高質(zhì)量iOS之熟悉OC
了解OC語(yǔ)言的起源
在類的頭文件中盡量少引入其他頭文件
多用字面語(yǔ)法,少用與之等價(jià)的方法
字面數(shù)值
字面量數(shù)組
字面量字典
局限性
多用類型常量,少用#define預(yù)處理指令
用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)碼
高質(zhì)量iOS之對(duì)象、消息、運(yùn)行期
理解”屬性“這一概念
屬性特質(zhì)
原子性
讀/寫權(quán)限
內(nèi)存管理語(yǔ)義
方法名
在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
理解“對(duì)象等同性”這一概念
特定類的等同性判定方法
等同性判定的執(zhí)行深度
容器中可變類的等同性
以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
理解objc_msgSend的作用
理解消息轉(zhuǎn)發(fā)機(jī)制
動(dòng)態(tài)方法解析
備援接收者
完整的消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)全流程
用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
理解“類對(duì)象”的用意
高質(zhì)量iOS之熟悉OC
了解OC語(yǔ)言的起源
OC語(yǔ)言由Smalltalk演化而來(lái),后者是消息型語(yǔ)言的鼻祖,使用“消息結(jié)構(gòu)”而非“函數(shù)調(diào)用”。
兩者之間的區(qū)別看上去就像上面這樣。
關(guān)鍵區(qū)別在于:使用消息結(jié)構(gòu)的語(yǔ)言,其運(yùn)行時(shí)所應(yīng)執(zhí)行的代碼由運(yùn)行環(huán)境來(lái)決定;而使用函數(shù)調(diào)用的語(yǔ)言,則由編譯器決定。
OC是C的“超集”,所以C語(yǔ)言中所有功能在編寫OC代碼時(shí)依然適用。理解C語(yǔ)言的內(nèi)存模型,有助于理解OC的內(nèi)存模型及其“引用計(jì)數(shù)”機(jī)制的工作原理。
OC語(yǔ)言中的指針是用來(lái)指示對(duì)象的,要聲明某個(gè)變量,令其指代某個(gè)對(duì)象,可用如下語(yǔ)法:
這種語(yǔ)法就是照搬C語(yǔ)言的,聲明了一個(gè)指向NSString的指針,所有OC對(duì)象都必須這樣聲明,因?yàn)?strong>對(duì)象所占內(nèi)存總是分配在“堆空間”中,而不會(huì)分配在“?!鄙?/strong>。
分配在堆中的內(nèi)存必須直接管理,而分配在棧上用于保存變量的內(nèi)存則會(huì)在其棧幀彈出時(shí)自動(dòng)清理。OC將對(duì)內(nèi)存管理抽象出來(lái)不再需要用malloc及free來(lái)分配1或釋放內(nèi)存,而是在運(yùn)行期把這部分工作抽象為一套內(nèi)存管理架構(gòu),名叫“引用計(jì)數(shù)”。
在OC代碼中,有時(shí)遇到定義里不含*的變量,它們可能使用“棧空間”,所保存的不是OC對(duì)象。
在類的頭文件中盡量少引入其他頭文件
OC與C和C++一樣也使用”頭文件“與”實(shí)現(xiàn)文件“來(lái)區(qū)隔代碼。用OC語(yǔ)言編寫任何類幾乎都需要引入Foudation.h。如果不引入這個(gè)文件,就要引入與其超類所屬框架相對(duì)應(yīng)的“基本頭文件”。比如UIViewController的子類的頭文件需要引入U(xiǎn)IKit.h
#import "EOCEmployer.h"
當(dāng)某個(gè)類需要聲明其他類為屬性時(shí),以前我們通常會(huì)使用上述代碼來(lái)引入某個(gè)類的頭文件。
但其實(shí)在編譯當(dāng)前類時(shí),我們并不需要知道它屬性中的類也就是EOCEmployer類的全部細(xì)節(jié),只要知道有一個(gè)類叫做EOCEmployer就好,可以采用以下辦法:
@class EOCEmployer;
這叫做“向前聲明”該類。
而當(dāng)前類的實(shí)現(xiàn)文件則需引入EOCEmployer類的頭文件,因?yàn)槿绻褂肊OCEmployer,就必須知道其所有接口細(xì)節(jié)。
這樣向前聲明不僅可以節(jié)約編譯時(shí)間,也解決了兩個(gè)類互相引用的問(wèn)題。
但是有時(shí)候必須引入其他頭文件:如果類繼承某個(gè)超類以及遵從某個(gè)協(xié)議時(shí),必須要引入定義那個(gè)超類或協(xié)議的頭文件。
因?yàn)檫@時(shí),協(xié)議必須有完整定義,要知道該協(xié)議中定義的方法。
除了例如“委托協(xié)議”的有些協(xié)議,最好把協(xié)議單獨(dú)放在一個(gè)頭文件中。
委托協(xié)議只有協(xié)議和接受協(xié)議委托類放在一起定義才有意義,此時(shí)最好在實(shí)現(xiàn)文件中聲明此類實(shí)現(xiàn)了該委托協(xié)議,并把這段實(shí)現(xiàn)的代碼放在分類中。這樣只要在實(shí)現(xiàn)文件引入包含委托協(xié)議的頭文件即可
多用字面語(yǔ)法,少用與之等價(jià)的方法
字面語(yǔ)法是一種更精簡(jiǎn)聲明NSString、NSNumber、NSArray、NSDictionary類的實(shí)例的語(yǔ)法,使用這種語(yǔ)法可以縮減源代碼長(zhǎng)度,使其更為易讀。
字面數(shù)值
以下是使用字面量來(lái)創(chuàng)建NSNumber的語(yǔ)法:
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//等號(hào)右邊還可以是表達(dá)式
int x = 5;
float y = 6.23f;
NSNumber *expressionNumber = @(x * y);
字面量數(shù)組
使用字面量創(chuàng)建數(shù)組:
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
//還可以使用字面量取下標(biāo)
NSString* dog = animals[1];
使用字面量語(yǔ)法,如果元素對(duì)象中有nil,則會(huì)拋出異常。
字面量字典
NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@23};
//取鍵值
NSString *lastName = personData[@"lastName"];
與字面量數(shù)組相同,如果有值為nil,便會(huì)拋出異常
如果數(shù)組和字典可變,那么也可以用字面語(yǔ)法來(lái)修改元素值。
局限性
字面量不適用于以上類的自定義子類。
并且字面語(yǔ)法創(chuàng)建出來(lái)的對(duì)象都是不可變的。
多用類型常量,少用#define預(yù)處理指令
編寫代碼時(shí)經(jīng)常要定義常量,有一種方法是使用#define預(yù)處理命令
#define ANIMATION_DURATION 0.3
這種方法一般可以實(shí)現(xiàn)想要的效果,但是這樣定義的常量沒(méi)有類型信息,并且如果使用這種方法,預(yù)處理會(huì)把碰到的所有ANIMATION_DURATION都替換成0.3,這樣的話,假設(shè)此指令聲明在某個(gè)頭文件中,那么所有引入了這個(gè)頭文件的代碼,ANIMATION_DURATION都會(huì)被替換。
因此定義常量是更好的選擇,比如:
static const NSTimeInterval kAnimationDuration = 0.3
這行代碼定義了一個(gè)類型為NSTimeInterval的常量
要注意常量名稱,常用的命名法是:若常量局限于某”編譯單元“(“實(shí)現(xiàn)文件”)之內(nèi),則在前面加字母k,若在類之外可見(jiàn),則也類名為前綴。
定義的位置也很重要,不應(yīng)定義在頭文件中,如果常量定義在頭文件中,相當(dāng)于聲明了一個(gè)全局變量,應(yīng)該加上前綴來(lái)表明所屬的類。
如果不打算公開(kāi)某個(gè)常量,則應(yīng)將其定義在使用該常量的實(shí)現(xiàn)文件里。
變量一定要同時(shí)用static與const來(lái)聲明。const確保變量不會(huì)被修改,而static將變量限制在當(dāng)前編譯單元。
如果常量需要公開(kāi),定義方式有所不同:
//header file
extern NSString *const EOCStringConstant
//implementation file
NSString *const EOCStringConstant = "VALUE";
extern這個(gè)關(guān)鍵字可以告訴編譯器在全局符號(hào)表中有一個(gè)名叫EOCStringConstant的符號(hào)。
由于要放在全局符號(hào)表里,所以命名要謹(jǐn)慎。為避免名稱沖突,最好用與之相關(guān)的類名作前綴。
用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)碼
在以一系列常量來(lái)表示錯(cuò)誤狀態(tài)碼或可組合的選項(xiàng)時(shí),宜使用枚舉為其命名。枚舉是一種常量命名方式,某對(duì)象經(jīng)歷的各種狀態(tài)可以定義為一個(gè)簡(jiǎn)單的枚舉集。比如:
編譯器為每個(gè)枚舉分配一個(gè)獨(dú)有的編號(hào),從0開(kāi)始,每個(gè)枚舉遞增1。
定義枚舉的語(yǔ)法如下:
enum EOCConnectionState state = EOCConnectionStateDisconnected;
//可用typedef關(guān)鍵字定義,這樣就不用每次敲入enum了
enum EOCConnectionState {EOCConectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef enum EOCConnectionState EOCConectionState;
可以指定枚舉使用哪種底層數(shù)據(jù)類型,語(yǔ)法如下:
enum EOCConnectionStateConnectionState : NSInteger;
//指定底層數(shù)據(jù)類型為NSInteger
//還可以不使用編譯器分配的序號(hào),手工指定某個(gè)枚舉成員對(duì)應(yīng)的值
enum EOCConnectionStateConnectionState {EOCConnectionStateDisconnected = 1,EOCConnectionStateConnecting,EOCConnectionStateConnected,
};
定義選項(xiàng)也應(yīng)使用枚舉類型,若選項(xiàng)可以彼此組合,更應(yīng)如此。只要枚舉定義得對(duì),各選項(xiàng)可通過(guò)“按位或操作符”來(lái)組合。比如:
enum UIViewAutoresizing {UIViewAutoresizingNone = 0,UIViewAutoresizingFlexibleLeftMargin = 1 << 0,UIViewAutoresizingFlexibleWidth = 1 << 1,UIViewAutoresizingFlexbleRightMargin = 1 << 2,UIViewAutoresizingFlexibleTopMargin = 1 << 3,UIViewAutoresizingFlexibleHeight = 1 << 4,UIViewAutoresizingFlexibleBottomMargin = 1 << 5,
}
這樣每個(gè)選項(xiàng)都可啟用或禁用,因?yàn)槊總€(gè)枚舉值對(duì)應(yīng)的二進(jìn)制表示中只有一位是1,可以用“按位與操作符”判斷是否已啟用某個(gè)選項(xiàng)。
Foundation框架中有一些宏可以用來(lái)定義這些枚舉類型并指定底層數(shù)據(jù)類型,用法如下:
typedef NS_ENUM(NSUInteger, EOCConnectionState) {EOCConnectionStateDisconnected,EOCConectionStateConnecting,EOCConectionStateConnected,
};
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {EOCPermittedDirectionUp ?= 1 << 0,EOCPermittedDirectionDown = 1 << 1,EOCPermittedDirectionLeft = 1 << 2,EOCPermittedDirectionRight = 1 << 3,
};
需要注意,凡是需要以按位或操作來(lái)組合的枚舉都應(yīng)使用NS_OPTIONS定義,若枚舉不需要互相組合,則應(yīng)使用NS_ENUM來(lái)定義。
枚舉還可以放在switch語(yǔ)句里,要注意在switch語(yǔ)句中,如果用枚舉來(lái)定義狀態(tài)集,則最好不要有default分支,這樣如果加入新的狀態(tài),會(huì)有警告信息。
高質(zhì)量iOS之對(duì)象、消息、運(yùn)行期
理解”屬性“這一概念
屬性是一種用@property語(yǔ)法來(lái)定義的用來(lái)封裝對(duì)象中的數(shù)據(jù)的變量。
屬性特質(zhì)
屬性特質(zhì)分為四類:
原子性
如果具備nonatomic特質(zhì),則不使用同步鎖,如不聲明nonatomic特質(zhì),默認(rèn)為atomic。
讀/寫權(quán)限
readwrite和readonly兩個(gè)特質(zhì)分別表示擁有存取方法和只有獲取方法
內(nèi)存管理語(yǔ)義
編譯器合成存取方法時(shí),要根據(jù)此特質(zhì)來(lái)決定生成的代碼。
assing “設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類型”的簡(jiǎn)單賦值
strong 先保留新值,再釋放舊值,再將新值設(shè)置上去
weak 不保留新值,不釋放舊值,與assign類似。屬性所指對(duì)象摧毀時(shí),屬性值清空
unsafe_unretained 與assign相同,但適用于對(duì)象類型,摧毀時(shí)不清空,與weak相反
copy 與strong類似,但不保留新值,而是將其“拷貝”
方法名
即存取方法
在實(shí)現(xiàn)初始化方法時(shí),一定遵循屬性定義中的語(yǔ)義
在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
在對(duì)象之外訪問(wèn)實(shí)例變量時(shí)總是應(yīng)該通過(guò)屬性來(lái)做,而在對(duì)象內(nèi)部,推薦的做法是:讀取實(shí)例變量時(shí)采取直接訪問(wèn)的形式,而設(shè)置實(shí)例變量時(shí)通過(guò)屬性來(lái)做。
有一些特殊情況:
1.在初始化方法中,總是應(yīng)該直接訪問(wèn)實(shí)例變量。如果待初始化的實(shí)例變量聲明在超類中,子類中無(wú)法直接訪問(wèn),則調(diào)用設(shè)置方法。
2.惰性初始化必須通過(guò)“獲取方法”來(lái)訪問(wèn)屬性,否則永遠(yuǎn)不會(huì)初始化。
理解“對(duì)象等同性”這一概念
對(duì)象等同性判定方法與==操作符不同,==比較的是兩個(gè)指針本身。"isEqual"方法可以判斷對(duì)象的等同性,某些對(duì)象有特殊的“等同性判定方法”。
NSObject協(xié)議中有兩個(gè)用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;
默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)指針值相等時(shí),對(duì)象才相等。
如果想覆寫方法,那就要理解其約定
如果"isEqual"方法判定相等,那么hash方法也必須返回同一個(gè)值,如果hash返回同一個(gè)值,isEqual方法未必判定相等。
編寫hash方法時(shí),應(yīng)該用當(dāng)前對(duì)象做做實(shí)驗(yàn),以便減少碰撞頻度與降低運(yùn)算復(fù)雜程度之間取舍。
特定類的等同性判定方法
在編寫判定方法時(shí),應(yīng)一并覆寫"isEqual"方法,實(shí)現(xiàn)方式為:如果受測(cè)的參數(shù)與接受該消息的對(duì)象都屬于一個(gè)類,就調(diào)用自己編寫的判定方法,否則交給超類來(lái)判斷。
等同性判定的執(zhí)行深度
創(chuàng)建等同性判定方法時(shí),需要決定是整個(gè)對(duì)象還是根據(jù)其中幾個(gè)字段。NSArray對(duì)比方法是,先看對(duì)象個(gè)數(shù),再對(duì)比每個(gè)對(duì)象,這叫做“深度等同性判定”??梢詣?chuàng)建標(biāo)識(shí)符來(lái)幫助判定等同性。
容器中可變類的等同性
在容器中放入可變類對(duì)象時(shí),放入collection之后,就不應(yīng)再改變其哈希碼了。
以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
“類族”是一種很有用的模式,可以隱藏“抽象基類”背后的實(shí)現(xiàn)細(xì)節(jié)。類族中的所有類基于一個(gè)基類,并且一般不允許直接創(chuàng)建。
判斷是否為類族中的類,不要檢測(cè)兩個(gè)類對(duì)象是否等同,而是應(yīng)該采用isKindOfClass:方法。
從公共抽象基類中繼承子類時(shí)應(yīng)先閱讀開(kāi)發(fā)文檔。
如NSArray的類族要新增子類,要遵守幾條規(guī)則:
1.子類應(yīng)繼承自類族中抽象基類
2.子類應(yīng)定義自己的數(shù)據(jù)存儲(chǔ)方式
3.子類應(yīng)覆寫超類文檔中指明需要覆寫的方法
在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
關(guān)聯(lián)對(duì)象相當(dāng)于把對(duì)象變成字典,不同的鍵對(duì)應(yīng)不同的值。設(shè)置關(guān)聯(lián)對(duì)象值時(shí),通常使用靜態(tài)全局變量做鍵。
存儲(chǔ)關(guān)聯(lián)對(duì)象時(shí),可以指明存儲(chǔ)策略,來(lái)維護(hù)內(nèi)存管理語(yǔ)義。
下列方法管理關(guān)聯(lián)對(duì)象:
理解objc_msgSend的作用
在對(duì)象上調(diào)用方法用OC術(shù)語(yǔ)來(lái)說(shuō)叫“傳遞信息”。OC中對(duì)象收到消息后,調(diào)用哪個(gè)方法于運(yùn)行期決定,因此是一門動(dòng)態(tài)語(yǔ)言。
OC中將方法轉(zhuǎn)變?yōu)橄⒄{(diào)用的是objc_msgSend這個(gè)函數(shù)
這個(gè)函數(shù)會(huì)依據(jù)接收者與選擇子的類型來(lái)調(diào)用適當(dāng)?shù)姆椒?。先搜尋方法列?#xff0c;若能找到相符合的,就跳至實(shí)現(xiàn)代碼,若找不到,就沿著繼承體系向上查找,找到合適的方法之后再跳轉(zhuǎn)。如果最后還找不到,就執(zhí)行“消息轉(zhuǎn)發(fā)”。
理解消息轉(zhuǎn)發(fā)機(jī)制
消息轉(zhuǎn)發(fā)分為兩大階段,第一階段先征詢接收者,看是否能動(dòng)態(tài)添加方法,這叫做“動(dòng)態(tài)方法解析”。第二階段分為兩小步:首先查看有沒(méi)有其他對(duì)象可以處理該消息,有的話就會(huì)轉(zhuǎn)給那個(gè)對(duì)象。如果沒(méi)有,就啟動(dòng)完整的轉(zhuǎn)發(fā)機(jī)制,吧消息封裝到NSInvocation對(duì)象中,令接收者設(shè)法解決當(dāng)前未處理的這條信息。
動(dòng)態(tài)方法解析
對(duì)象收到無(wú)法解決的消息后,首先調(diào)用類方法:
使用這種方法的前提是:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫好,只等著運(yùn)行時(shí)動(dòng)態(tài)插在類里面。
備援接收者
當(dāng)前接收者可以處理未知的選擇子,這一步系統(tǒng)會(huì)問(wèn)接收者是否可以把這條消息裝給其他接收者來(lái)處理:
若找到備援對(duì)象,就將其返回;若找不到,就返回nil。
我們無(wú)法操作這一步轉(zhuǎn)發(fā)的消息,若想先修改消息內(nèi)容再發(fā)送,就得啟用完整的消息轉(zhuǎn)發(fā)機(jī)制。
完整的消息轉(zhuǎn)發(fā)
這里將消息封裝在NSInvacation對(duì)象中,并調(diào)用方法來(lái)轉(zhuǎn)發(fā)信息:
消息轉(zhuǎn)發(fā)全流程
每一步均有機(jī)會(huì)處理消息,步驟越往后,處理消息的代價(jià)就越大。
用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”
可以在運(yùn)行期改變對(duì)象給定的選擇子名稱對(duì)應(yīng)的方法,這被稱為“方法調(diào)配”。
比如可以交換兩個(gè)方法實(shí)現(xiàn),可以通過(guò)下列方法:
通過(guò)此方案,可以為那些完全不知道具體實(shí)現(xiàn)的黑盒方法增加日志記錄功能,有助于程序調(diào)試。
理解“類對(duì)象”的用意
描述OC對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)定義在運(yùn)行期程序庫(kù)的頭文件里,id類型也定義在這里:
該結(jié)構(gòu)體首個(gè)成員是Class類變量,定義了對(duì)象的類,稱為"isa"指針。Class對(duì)象也定義在這個(gè)頭文件中:
說(shuō)明Class本身也是OC對(duì)象,類對(duì)象所屬的類型是”元類“。每個(gè)類僅有一個(gè)類對(duì)象。
在查詢類型信息時(shí),盡量使用類型信息查詢方法,而不要直接比較兩個(gè)類對(duì)象是否等同。