如何優(yōu)雅的談?wù)揙C

在面試中,我們經(jīng)常會(huì)遇到一些原理性的問題,很常識(shí)但很難用通俗的語(yǔ)言解釋清楚,這也是大部分業(yè)務(wù)級(jí)程序員經(jīng)常失誤的地方。雖然寫了多年代碼,但是核心思想不清,導(dǎo)致自己的后續(xù)發(fā)展受限,這是一個(gè)優(yōu)秀的程序員和普通程序員的分水嶺。要知其然而知其所以然!這也是整理這篇文章的初衷。文中結(jié)合了之前的一些文章,條理更清晰,內(nèi)容更深入。

本文包括:

  • OC的面向?qū)ο?/li>
  • 運(yùn)行時(shí)Runtime
  • 運(yùn)行循環(huán)RunLoop
  • 事件響應(yīng)鏈
  • 引用計(jì)數(shù)
  • 生命周期
  • 與其他語(yǔ)言的區(qū)別

Objective-C 簡(jiǎn)稱OC(下面以此代稱),是在C語(yǔ)言的基礎(chǔ)上,增加了一層最小的面向?qū)ο笳Z(yǔ)言。是一種靜態(tài)輸入的語(yǔ)言,即“必須先聲明數(shù)據(jù)中每個(gè)變量(或者容器)的數(shù)據(jù)類型”。但它是一個(gè)動(dòng)態(tài)語(yǔ)言,代碼中的某一部分可以在app運(yùn)行的時(shí)候被擴(kuò)展和修改(比如,在被編譯之后)。OC完全兼容C語(yǔ)言,在代碼中,可以混用c,甚至是c++代碼。

面向?qū)ο笕瓌t(封裝,繼承,多態(tài))

面向?qū)ο缶哂兴膫€(gè)基本特征:抽象,封裝,繼承和多態(tài)。

C語(yǔ)言是面向過程的語(yǔ)言(關(guān)注的是函數(shù)),OC,C++,JAVA,C#,PHP,Swift是面向?qū)ο蟮模嫦蜻^程關(guān)注的是解決問題涉及的步驟,而面向?qū)ο箨P(guān)注的是設(shè)計(jì)能夠?qū)崿F(xiàn)解決問題所需功能的類。抽象是面向?qū)ο蟮乃枷牖A(chǔ)。

抽象包括兩個(gè)方面,一是過程抽象,二是數(shù)據(jù)抽象。過程抽象是指任何一個(gè)明確定義功能的操作都可被使用者看作單個(gè)的實(shí)體看待,盡管這個(gè)操作實(shí)際上可能由一系列更低級(jí)的操作來完成。數(shù)據(jù)抽象定義了數(shù)據(jù)類型和施加于該類型對(duì)象上的操作,并限定了對(duì)象的值只能通過使用這些操作修改和觀察。抽象是一種思想,封裝繼承和多態(tài)是這種思想的實(shí)現(xiàn)。

封裝

封裝是把過程和數(shù)據(jù)包圍起來(即函數(shù)和數(shù)據(jù)結(jié)構(gòu),函數(shù)是行為,數(shù)據(jù)結(jié)構(gòu)是描述),有限制的對(duì)數(shù)據(jù)的訪問。面向?qū)ο蠡谶@個(gè)基本概念開始的(因?yàn)槊嫦驅(qū)ο蟾⒅氐氖穷?/em>),即現(xiàn)實(shí)世界可以被描繪成一系列完全自治、封裝的對(duì)象,這些對(duì)象通過一個(gè)受保護(hù)的接口訪問其他對(duì)象。一旦定義了一個(gè)對(duì)象的特性,則有必要決定這些特性的可見性,封裝保證了模塊具有較好的獨(dú)立性,使得程序維護(hù)修改較為容易。對(duì)應(yīng)用程序的修改僅限于類的內(nèi)部,因而可以將應(yīng)用程序修改帶來的影響減少到最低限度。但是封裝會(huì)導(dǎo)致并行效率問題,因?yàn)閳?zhí)行部分和數(shù)據(jù)部分被綁定在一起,制約了并行程度。面向?qū)ο笏枷雽⒑瘮?shù)和數(shù)據(jù)綁在一起,擴(kuò)大了代碼重用時(shí)的粒度。而且封裝下的拆箱裝箱過程中也會(huì)導(dǎo)致內(nèi)存的浪費(fèi)。

繼承

繼承是一種層次模型,允許和鼓勵(lì)類的重用,并提供了一種明確表述共性的方法。新類繼承了原始類的特性,新類稱為原始類的派生類(子類和父類)。派生類可以從它的基類那里繼承方法和實(shí)例變量,并且類可以修改或增加新的方法使之更適合特殊的需要。繼承性很好的解決了軟件的可重用性問題。但是,不恰當(dāng)?shù)厥褂美^承導(dǎo)致的最大的一個(gè)缺陷特征就是高耦合(即“牽一發(fā)而動(dòng)全身”,是設(shè)計(jì)類時(shí)層次沒分清導(dǎo)致的)。解決方案是用組合替代繼承。將模塊拆開,然后通過定義好的接口進(jìn)行交互,一般來說可以選擇Delegate模式來交互。使用繼承其實(shí)是如何給一類對(duì)象劃分層次的問題。在正確的繼承方式中,父類應(yīng)當(dāng)扮演的是底層的角色,子類是上層的業(yè)務(wù)。父類只是給子類提供服務(wù),并不涉及子類的業(yè)務(wù)邏輯;層級(jí)關(guān)系明顯,功能劃分清晰;父類的所有變化,都需要在子類中體現(xiàn),此時(shí)耦合已經(jīng)成為需求。

多態(tài)

多態(tài)性是指允許不同類的對(duì)象對(duì)同一消息作出響應(yīng)。多態(tài)性包括參數(shù)化多態(tài)性和包含多態(tài)性。很好的解決了應(yīng)用程序函數(shù)同名問題,多態(tài)一般都要跟繼承結(jié)合起來說,其本質(zhì)是子類通過覆蓋或重載父類的方法,來使得對(duì)同一類對(duì)象同一方法的調(diào)用產(chǎn)生不同的結(jié)果。覆蓋是對(duì)接口方法的實(shí)現(xiàn),繼承中也可能會(huì)在子類覆蓋父類中的方法。重載,是指我們可以定義一些名稱相同的方法,通過定義不同的輸入?yún)?shù)來區(qū)分這些方法,然后再調(diào)用時(shí),VM就會(huì)根據(jù)不同的參數(shù)樣式,來選擇合適的方法執(zhí)行。在使用重載時(shí)只能通過不同的參數(shù)樣式。例如,不同的參數(shù)類型,不同的參數(shù)個(gè)數(shù),不同的參數(shù)順序(當(dāng)然,同一方法內(nèi)的幾個(gè)參數(shù)類型必須不一樣); 但繼承會(huì)引入多態(tài)使用混亂的境況并產(chǎn)生耦合,更好的方法是使用接口。通過IOP將子類與可能被子類引入的不相關(guān)邏輯剝離開來,提高了子類的可重用性,降低了遷移時(shí)可能的耦合。接口規(guī)范了子類哪些必須實(shí)現(xiàn),哪些可選實(shí)現(xiàn)。那些不在接口定義的方法列表里的父類方法,事實(shí)上就是不建議覆重的方法。如果引入多態(tài)之后導(dǎo)致對(duì)象角色不夠單純,那就不應(yīng)當(dāng)引入多態(tài),如果引入多態(tài)之后依舊是單純角色,那就可以引入多態(tài);如果要覆重的方法是角色業(yè)務(wù)的其中一個(gè)組成部分,那么就最好不要用多態(tài)的方案,用IOP,因?yàn)樵谕饨缯{(diào)用的時(shí)候其實(shí)并不需要通過多態(tài)來滿足定制化的需求。

動(dòng)態(tài)性(Runtime)

Objective-C 是面相運(yùn)行時(shí)的語(yǔ)言,它會(huì)盡可能的把編譯和鏈接時(shí)要執(zhí)行的邏輯延遲到運(yùn)行時(shí)。使用Runtime可以按需要把消息重定向給合適的對(duì)象,交換方法的實(shí)現(xiàn)等等。

Runtime簡(jiǎn)稱運(yùn)行時(shí),其中最主要的是消息機(jī)制,是一個(gè)主要使用 C 和匯編寫的庫(kù),為 C 添加了面相對(duì)象的能力并創(chuàng)造了 Objective-C。。OC的函數(shù)調(diào)用稱為消息發(fā)送。屬于動(dòng)態(tài)調(diào)用過程。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)(在編 譯階段,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過就不會(huì)報(bào)錯(cuò)。而C語(yǔ)言在編譯階段就會(huì)報(bào)錯(cuò))。只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找 到對(duì)應(yīng)的函數(shù)來調(diào)用。

如:

[obj makeText];
==》
objc_msgSend(obj,@selector(makeText));

編譯器執(zhí)行上述轉(zhuǎn)換。在objc_msgSend函數(shù)中,首先通過obj的isa指針找到obj對(duì)應(yīng)的class。每個(gè)對(duì)象內(nèi)部都默認(rèn)有一個(gè)isa指針指向這個(gè)對(duì)象所使用的類。isa是對(duì)象中的隱藏指針,指向創(chuàng)建這個(gè)對(duì)象的類。在Class中先去cache中通過SEL查找對(duì)應(yīng)函數(shù)method(cache中method列表是以SEL為key通過hash表來存儲(chǔ)的,這樣能提高函數(shù)查找速度),若cache中未找到,再去methodList中查找,若methodlist中未找到,則取superClass中查找。若能找到,則將method加入到cache中,以方便下次查找,并通過method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行。

動(dòng)態(tài)性的三方面

OC的動(dòng)態(tài)特性表現(xiàn)為了三個(gè)方面:動(dòng)態(tài)類型、動(dòng)態(tài)綁定、動(dòng)態(tài)加載。之所以叫做動(dòng)態(tài),是因?yàn)楸仨毜竭\(yùn)行時(shí)(runtime)才會(huì)做一些事情。

動(dòng)態(tài)類型,就是id類型。動(dòng)態(tài)類型是跟靜態(tài)類型相對(duì)的。內(nèi)置的基本類型都屬于靜態(tài)類型(int、NSString等)。靜態(tài)類型在編譯的時(shí)候就能被識(shí)別出來(即前面說的靜態(tài)輸入)。所以,若程序發(fā)生了類型不對(duì)應(yīng),編譯器就會(huì)發(fā)出警告。而動(dòng)態(tài)類型就編譯器編譯的時(shí)候是不能被識(shí)別的,要等到運(yùn)行時(shí)(runtime),即程序運(yùn)行的時(shí)候才會(huì)根據(jù)語(yǔ)境來識(shí)別。所以這里面就有兩個(gè)概念要分清:編譯時(shí)跟運(yùn)行時(shí)。

動(dòng)態(tài)語(yǔ)言和靜態(tài)語(yǔ)言的一個(gè)區(qū)別是靜態(tài)語(yǔ)言提前編譯好文件,即所有的邏輯已在編譯時(shí)確定,運(yùn)行時(shí)直接加載編譯后的文件;而動(dòng)態(tài)語(yǔ)言是在運(yùn)行時(shí)才確定實(shí)現(xiàn)。典型的靜態(tài)語(yǔ)言是C++,動(dòng)態(tài)語(yǔ)言包括OC,JAVA,C#等;因?yàn)殪o態(tài)語(yǔ)言提前編譯好了執(zhí)行文件,也就是通常所說的靜態(tài)語(yǔ)言效率較高的原因。

動(dòng)態(tài)綁定(dynamic binding)需要用到@selector/SEL。先來看看“函數(shù)”,對(duì)于其他一些靜態(tài)語(yǔ)言,比如c++,一般在編譯的時(shí)候就已經(jīng)將要調(diào)用的函數(shù)的函數(shù)簽名都告訴編譯器了。靜態(tài)的,不能改變。而在OC中,其實(shí)是沒有函數(shù)的概念的,我們叫“消息機(jī)制”,所謂的函數(shù)調(diào)用就是給對(duì)象發(fā)送一條消息。這時(shí),動(dòng)態(tài)綁定的特性就來了。OC可以先跳過編譯,到運(yùn)行的時(shí)候才動(dòng)態(tài)地添加函數(shù)調(diào)用,在運(yùn)行時(shí)才決定要調(diào)用什么方法,需要傳什么參數(shù)進(jìn)去,這就是動(dòng)態(tài)綁定。要實(shí)現(xiàn)他就必須用SEL變量綁定一個(gè)方法。最終形成的這個(gè)SEL變量就代表一個(gè)方法的引用。這里要注意一點(diǎn):SEL并不是C里面的函數(shù)指針,雖然很像,但真心不是函數(shù)指針。SEL變量只是一個(gè)整數(shù),他是該方法的ID。以前的函數(shù)調(diào)用,是根據(jù)函數(shù)名,也就是字符串去查找函數(shù)體。但現(xiàn)在,我們是根據(jù)一個(gè)ID整數(shù)來查找方法,整數(shù)的查找自然要比字符串的查找快得多!所以,動(dòng)態(tài)綁定的特定不僅方便,而且效率更高。

動(dòng)態(tài)加載就是根據(jù)需求動(dòng)態(tài)地加載資源,在運(yùn)行時(shí)加載新類。在運(yùn)行時(shí)創(chuàng)建一個(gè)新類,只需要3步:

1、為 class pair分配存儲(chǔ)空間 ,使用 objc_allocateClassPair函數(shù)

2、增加需要的方法使用class_addMethod函數(shù),增加實(shí)例變量用class_addIvar

3 、用objc_registerClassPair函數(shù)注冊(cè)這個(gè)類,以便它能被別人使用。

Method Swizzling

在Objective-C中調(diào)用一個(gè)方法,其實(shí)是向一個(gè)對(duì)象發(fā)送消息,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動(dòng)態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn),達(dá)到給方法掛鉤的目的。每個(gè)類都有一個(gè)方法列表,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系。IMP類似函數(shù)指針,指向具體的Method實(shí)現(xiàn)。

用 method_exchangeImplementations 來交換2個(gè)方法中的IMP,
用 class_replaceMethod 來修改類,
用 method_setImplementation 來直接設(shè)置某個(gè)方法的IMP,歸根結(jié)底,都是偷換了selector的IMP。

RunLoop

RunLoop是一讓線程能隨時(shí)處理事件但不退出的機(jī)制。RunLoop實(shí)際上是一個(gè)對(duì)象,這個(gè)對(duì)象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行Event Loop的邏輯。線程執(zhí)行了這個(gè)函數(shù)后,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。讓線程在沒有處理消息時(shí)休眠以避免資源占用、在有消息到來時(shí)立刻被喚醒。一個(gè)runloop就是一個(gè)事件處理循環(huán),用來不停的監(jiān)聽和處理輸入事件并將其分配到對(duì)應(yīng)的目標(biāo)上進(jìn)行處理。

RunLoop的四個(gè)作用為:使程序一直運(yùn)行接受用戶輸入;決定程序在何時(shí)應(yīng)該處理哪些Event;調(diào)用解耦;節(jié)省CPU時(shí)間。

線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取,那它一直都不會(huì)有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其RunLoop(主線程除外)。

主線程的runloop默認(rèn)是啟動(dòng)的。

OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對(duì)象:NSRunLoop和CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的。NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的。

NSRunLoop是一種更加高明的消息處理模式,在對(duì)消息處理過程進(jìn)行了更好的抽象和封裝,不用處理一些很瑣碎很低層次的具體消息的處理,在NSRunLoop中每一個(gè)消息就被打包在input source或者是timer source中了。使用run loop可以使你的線程在有工作的時(shí)候工作,沒有工作的時(shí)候休眠,可以大大節(jié)省系統(tǒng)資源。

對(duì)其它線程來說,runloop默認(rèn)是沒有啟動(dòng)的,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng),如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要。在任何一個(gè)Cocoa程序的線程中,都可以通過:

NSRunLoop   *runloop = [NSRunLoop currentRunLoop];

獲取到當(dāng)前線程的runloop。

Cocoa中的NSRunLoop類并不是線程安全的
我們不能在一個(gè)線程中去操作另外一個(gè)線程的runloop對(duì)象,那很可能會(huì)造成意想不到的后果。但是CoreFundation中的不透明類CFRunLoopRef是線程安全的,而且兩種類型的runloop完全可以混合使用。Cocoa中的NSRunLoop類可以通過實(shí)例方法:

 - (CFRunLoopRef)getCFRunLoop;

獲取對(duì)應(yīng)的CFRunLoopRef類,來達(dá)到線程安全的目的。

Runloop的管理并不完全是自動(dòng)的。我們?nèi)员仨氃O(shè)計(jì)線程代碼以在適當(dāng)?shù)臅r(shí)候啟動(dòng)runloop并正確響應(yīng)輸入事件,當(dāng)然前提是線程中需要用到runloop。而且,我們還需要使用while/for語(yǔ)句來驅(qū)動(dòng)runloop能夠循環(huán)運(yùn)行,下面的代碼就成功驅(qū)動(dòng)了一個(gè)run loop:

BOOL isRunning = NO;
do {
 isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);

Runloop同時(shí)也負(fù)責(zé)autorelease pool的創(chuàng)建和釋放
在使用手動(dòng)的內(nèi)存管理方式的項(xiàng)目中,會(huì)經(jīng)常用到很多自動(dòng)釋放的對(duì)象,如果這些對(duì)象不能夠被即時(shí)釋放掉,會(huì)造成內(nèi)存占用量急劇增大。Runloop就為我們做了這樣的工作,每當(dāng)一個(gè)運(yùn)行循環(huán)結(jié)束的時(shí)候,它都會(huì)釋放一次autorelease pool,同時(shí)pool中的所有自動(dòng)釋放類型變量都會(huì)被釋放掉。

系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:

kCFRunLoopDefaultMode: App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的。

UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響。

UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用。

GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到。

kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode,沒有實(shí)際作用。

輪播圖中的NSTimer問題
創(chuàng)建定時(shí)器:

 1:NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];

此方法創(chuàng)建的定時(shí)器,必須加到NSRunLoop中。

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
[runLoop addTimer:timer forMode: NSRunLoopCommonModes];

forMode的參數(shù)有兩種類型可供選擇:<code> NSDefaultRunLoopMode , NSRunLoopCommonModes</code>,第一個(gè)參數(shù)為默認(rèn)參數(shù),當(dāng)下面有textView,textfield等控件時(shí),拖拽控件,此時(shí)輪播器會(huì)停止輪播,是因?yàn)镹SRunLoop的原因,NSRunLoop為一個(gè)死循環(huán),實(shí)時(shí)監(jiān)測(cè)有無事件響應(yīng),如果當(dāng)前線程就是主線程,也就是UI線程時(shí),某些UI事件,比如UIScrollView的拖動(dòng)操作,會(huì)將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個(gè)過程中,默認(rèn)的NSDefaultRunLoopMode模式中注冊(cè)的事件是不會(huì)被執(zhí)行的。NSRunLoopCommonModes 能夠在多線程中起作用,這個(gè)模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(jié)合,這也是將modes換為NSRunLoopCommonModes便可解決的原因。

2: self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(changeImage) userInfo:nil repeats:YES];

此種創(chuàng)建定時(shí)器的方式,默認(rèn)加到了runloop。

main函數(shù)的運(yùn)行

在main.m中:

 int main(int argc, char *argv[])
 {
    @autoreleasepool {
      return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
   }
 }

UIApplicationMain() 函數(shù)會(huì)為main thread 設(shè)置一個(gè)NSRunLoop 對(duì)象,這就解釋了app應(yīng)用可以在無人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)。

僅當(dāng)在為你的程序創(chuàng)建輔助線程的時(shí)候,你才需要顯式運(yùn)行一個(gè)runloop。Runloop是程序主線程基礎(chǔ)設(shè)施的關(guān)鍵部分,所以,Cocoa和Carbon程序提供了代碼運(yùn)行主程序的循環(huán)并自動(dòng)啟動(dòng)runloop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)作為程序啟動(dòng)步驟的一部分,它在程序正常啟動(dòng)的時(shí)候就會(huì)啟動(dòng)程序的主循環(huán)。如果你使用xcode提供的模板創(chuàng)建你的程序,那你永遠(yuǎn)不需要自己去顯式的調(diào)用這些例程。

對(duì)于輔助線程,你需要判斷一個(gè)runloop是否是必須的。如果是必須的,那么你要自己配置并啟動(dòng)它。你不需要在任何情況下都去啟動(dòng)一個(gè)線程的runloop。比如,你使用線程來處理一個(gè)預(yù)先定義的長(zhǎng)時(shí)間運(yùn)行的任務(wù)時(shí),你應(yīng)該避免啟動(dòng)runloop。Runloop在你要和線程有更多的交互時(shí)才需要,比如以下情況:

1.使用端口或自定義輸入源來和其他線程通信;

2.使用線程的定時(shí)器;

3.Cocoa中使用任何performSelector...的方法;

4.使線程周期性工作;

事件響應(yīng)鏈

對(duì)于IOS設(shè)備用戶來說,操作設(shè)備的方式主要有三種:觸摸屏幕、晃動(dòng)設(shè)備、通過遙控設(shè)施控制設(shè)備。對(duì)應(yīng)的事件類型有以下三種:

1、觸屏事件(Touch Event)

2、運(yùn)動(dòng)事件(Motion Event)

3、遠(yuǎn)端控制事件(Remote-Control Event)

事件的傳遞和響應(yīng)分兩個(gè)鏈:

傳遞鏈:由系統(tǒng)向離用戶最近的view傳遞。

UIKit –> active app’s event queue –> window –> root view –>……–>lowest view

響應(yīng)鏈:由離用戶最近的view向系統(tǒng)傳遞。

initial view –> super view –> …..–> view controller –> window –> Application

響應(yīng)者鏈(Responder Chain):由多個(gè)響應(yīng)者對(duì)象連接起來的鏈條,作用是能很清楚的看見每個(gè)響應(yīng)者之間的聯(lián)系,并且可以讓一個(gè)事件多個(gè)對(duì)象處理。

響應(yīng)者對(duì)象(Responder Object),指的是有響應(yīng)和處理事件能力的對(duì)象。響應(yīng)者鏈就是由一系列的響應(yīng)者對(duì)象構(gòu)成的一個(gè)層次結(jié)構(gòu)。

UIResponder是所有響應(yīng)對(duì)象的基類,在UIResponder類中定義了處理上述各種事件的接口。我們熟悉的UIApplication、 UIViewController、UIWindow和所有繼承自UIView的UIKit類都直接或間接的繼承自UIResponder,所以它們的實(shí)例都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對(duì)象。

響應(yīng)者鏈有以下特點(diǎn):

1、響應(yīng)者鏈通常是由視圖(UIView)構(gòu)成的;

2、一個(gè)視圖的下一個(gè)響應(yīng)者是它視圖控制器(UIViewController)(如果有的話),然后再轉(zhuǎn)給它的父視圖(Super View);

3、視圖控制器(如果有的話)的下一個(gè)響應(yīng)者為其管理的視圖的父視圖;

4、單例的窗口(UIWindow)的內(nèi)容視圖將指向窗口本身作為它的下一個(gè)響應(yīng)者,Cocoa Touch應(yīng)用不像Cocoa應(yīng)用,它只有一個(gè)UIWindow對(duì)象,因此整個(gè)響應(yīng)者鏈要簡(jiǎn)單一點(diǎn);

5、單例的應(yīng)用(UIApplication)是一個(gè)響應(yīng)者鏈的終點(diǎn),它的下一個(gè)響應(yīng)者指向nil,以結(jié)束整個(gè)循環(huán)。

iOS系統(tǒng)檢測(cè)到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對(duì)象,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對(duì)象首先會(huì)使用<code>hitTest:withEvent:</code>方法尋找此次Touch操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖,這個(gè)過程稱之為hit-test view。

UIWindow實(shí)例對(duì)象會(huì)首先在它的內(nèi)容視圖上調(diào)用<code>hitTest:withEvent:</code>,此方法會(huì)在其視圖層級(jí)結(jié)構(gòu)中的每個(gè)視圖上調(diào)用<code>pointInside:withEvent:</code>(該方法用來判斷點(diǎn)擊事件發(fā)生的位置是否處于當(dāng)前視圖范圍內(nèi),以確定用戶是不是點(diǎn)擊了當(dāng)前視圖),如果<code>pointInside:withEvent:</code>返回YES,則繼續(xù)逐級(jí)調(diào)用,直到找到touch操作發(fā)生的位置,這個(gè)視圖也就是要找的hit-test view。

<code>hitTest:withEvent:</code>方法的處理流程如下:

首先調(diào)用當(dāng)前視圖的<code>pointInside:withEvent:</code>方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi);若返回NO,則<code>hitTest:withEvent:</code>返回nil;若返回YES,則向當(dāng)前視圖的所有子視圖(subviews)發(fā)送<code>hitTest:withEvent:</code>消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對(duì)象或者全部子視圖遍歷完畢;若第一次有子視圖返回非空對(duì)象,則<code>hitTest:withEvent:</code>方法返回此對(duì)象,處理結(jié)束;如所有子視圖都返回非,則<code>hitTest:withEvent:</code>方法返回自身(self)。

引用計(jì)數(shù)器(ARC 和 MRC)

ARC:自動(dòng)引用計(jì)數(shù)器(Automatic Reference Counting)

MRC:手動(dòng)引用計(jì)算器(由于現(xiàn)在幾乎不用了,不做過多解說)

Objective-c中提供了兩種內(nèi)存管理機(jī)制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分別提供對(duì)內(nèi)存的手動(dòng)和自動(dòng)管理,來滿足不同的需求。Xcode 4.1及其以前版本沒有ARC。

在MRC的內(nèi)存管理模式下,與對(duì)變量的管理相關(guān)的方法有:retain,release和autorelease。retain和release方法操作的是引用記數(shù),當(dāng)引用記數(shù)為零時(shí),便自動(dòng)釋放內(nèi)存。并且可以用NSAutoreleasePool對(duì)象,對(duì)加入自動(dòng)釋放池(autorelease調(diào)用)的變量進(jìn)行管理,當(dāng)內(nèi)存緊張時(shí)回收內(nèi)存。
(1) retain,該方法的作用是將內(nèi)存數(shù)據(jù)的所有權(quán)附給另一指針變量,引用數(shù)加1,即retainCount+= 1;
(2) release,該方法是釋放指針變量對(duì)內(nèi)存數(shù)據(jù)的所有權(quán),引用數(shù)減1,即retainCount-= 1;
(3) autorelease,該方法是將該對(duì)象內(nèi)存的管理放到autoreleasepool中。

在ARC中與內(nèi)存管理有關(guān)的標(biāo)識(shí)符,可以分為變量標(biāo)識(shí)符和屬性標(biāo)識(shí)符,對(duì)于變量默認(rèn)為<code>__strong</code>,而對(duì)于屬性默認(rèn)為<code>unsafe_unretained</code>。也存在autoreleasepool。

其中assign/retain/copy與MRC下property的標(biāo)識(shí)符意義相同,strong類似與retain,assign類似于<code>unsafe_unretained</code>,<code>strong/weak/unsafe_unretained</code>與ARC下變量標(biāo)識(shí)符意義相同,只是一個(gè)用于屬性的標(biāo)識(shí),一個(gè)用于變量的標(biāo)識(shí)(帶兩個(gè)下劃短線__)。

生命周期

app應(yīng)用程序有5種狀態(tài):

Not running未運(yùn)行:程序沒啟動(dòng)。

Inactive未激活:程序在前臺(tái)運(yùn)行,不過沒有接收到事件。在沒有事件處理情況下程序通常停留在這個(gè)狀態(tài)。

Active激活:程序在前臺(tái)運(yùn)行而且接收到了事件。這也是前臺(tái)的一個(gè)正常的模式。

Backgroud后臺(tái):程序在后臺(tái)而且能執(zhí)行代碼,大多數(shù)程序進(jìn)入這個(gè)狀態(tài)后會(huì)在在這個(gè)狀態(tài)上停留一會(huì)。時(shí)間到之后會(huì)進(jìn)入掛起狀態(tài)(Suspended)。有的程序經(jīng)過特殊的請(qǐng)求后可以長(zhǎng)期處于Backgroud狀態(tài)。

Suspended掛起:程序在后臺(tái)不能執(zhí)行代碼。系統(tǒng)會(huì)自動(dòng)把程序變成這個(gè)狀態(tài)而且不會(huì)發(fā)出通知。當(dāng)掛起時(shí),程序還是停留在內(nèi)存中的,當(dāng)系統(tǒng)內(nèi)存低時(shí),系統(tǒng)就把掛起的程序清除掉,為前臺(tái)程序提供更多的內(nèi)存。

iOS的入口在main.m文件的main函數(shù),根據(jù)UIApplicationMain函數(shù),程序?qū)⑦M(jìn)入AppDelegate.m,這個(gè)文件是xcode新建工程時(shí)自動(dòng)生成的。AppDelegate.m文件,關(guān)乎著應(yīng)用程序的生命周期。

1、application didFinishLaunchingWithOptions:當(dāng)應(yīng)用程序啟動(dòng)時(shí)執(zhí)行,應(yīng)用程序啟動(dòng)入口,只在應(yīng)用程序啟動(dòng)時(shí)執(zhí)行一次。若用戶直接啟動(dòng),lauchOptions內(nèi)無數(shù)據(jù),若通過其他方式啟動(dòng)應(yīng)用,lauchOptions包含對(duì)應(yīng)方式的內(nèi)容。

2、applicationWillResignActive:在應(yīng)用程序?qū)⒁苫顒?dòng)狀態(tài)切換到非活動(dòng)狀態(tài)時(shí)候,要執(zhí)行的委托調(diào)用,如 按下 home 按鈕,返回主屏幕,或全屏之間切換應(yīng)用程序等。

3、applicationDidEnterBackground:在應(yīng)用程序已進(jìn)入后臺(tái)程序時(shí),要執(zhí)行的委托調(diào)用。

4、applicationWillEnterForeground:在應(yīng)用程序?qū)⒁M(jìn)入前臺(tái)時(shí)(被激活),要執(zhí)行的委托調(diào)用,剛好與applicationWillResignActive 方法相對(duì)應(yīng)。

5、applicationDidBecomeActive:在應(yīng)用程序已被激活后,要執(zhí)行的委托調(diào)用,剛好與applicationDidEnterBackground 方法相對(duì)應(yīng)。

6、applicationWillTerminate:在應(yīng)用程序要完全推出的時(shí)候,要執(zhí)行的委托調(diào)用,這個(gè)需要要設(shè)置UIApplicationExitsOnSuspend的鍵值。

初次啟動(dòng):

iOS_didFinishLaunchingWithOptions

iOS_applicationDidBecomeActive

按下home鍵:

iOS_applicationWillResignActive

iOS_applicationDidEnterBackground

點(diǎn)擊程序圖標(biāo)進(jìn)入:

iOS_applicationWillEnterForeground

iOS_applicationDidBecomeActive

當(dāng)應(yīng)用程序進(jìn)入后臺(tái)時(shí),應(yīng)該保存用戶數(shù)據(jù)或狀態(tài)信息,所有沒寫到磁盤的文件或信息,在進(jìn)入后臺(tái)時(shí),最后都寫到磁盤去,因?yàn)槌绦蚩赡茉诤笈_(tái)被殺死。釋放盡可能釋放的內(nèi)存。

- (void)applicationDidEnterBackground:(UIApplication *)application

方法有大概5秒的時(shí)間讓你完成這些任務(wù)。如果超過時(shí)間還有未完成的任務(wù),你的程序就會(huì)被終止而且從內(nèi)存中清除。

如果還需要長(zhǎng)時(shí)間的運(yùn)行任務(wù),可以在該方法中調(diào)用

[application beginBackgroundTaskWithExpirationHandler:^{ 

NSLog(@"begin Background Task With Expiration Handler"); 

}];

程序終止

程序只要符合以下情況之一,只要進(jìn)入后臺(tái)或掛起狀態(tài)就會(huì)終止:

①iOS4.0以前的系統(tǒng)

②app是基于iOS4.0之前系統(tǒng)開發(fā)的。

③設(shè)備不支持多任務(wù)

④在Info.plist文件中,程序包含了 UIApplicationExitsOnSuspend 鍵。

系統(tǒng)常常是為其他app啟動(dòng)時(shí)由于內(nèi)存不足而回收內(nèi)存最后需要終止應(yīng)用程序,但有時(shí)也會(huì)是由于app很長(zhǎng)時(shí)間才響應(yīng)而終止。如果app當(dāng)時(shí)運(yùn)行在后臺(tái)并且沒有暫停,系統(tǒng)會(huì)在應(yīng)用程序終止之前調(diào)用app的代理的方法 <code>- (void)applicationWillTerminate:(UIApplication *)application</code>,這樣可以讓你可以做一些清理工作。你可以保存一些數(shù)據(jù)或app的狀態(tài)。這個(gè)方法也有5秒鐘的限制。超時(shí)后方法會(huì)返回程序從內(nèi)存中清除。用戶可以手工關(guān)閉應(yīng)用程序。

和其他動(dòng)態(tài)語(yǔ)言的區(qū)別

OC中方法的實(shí)現(xiàn)只能寫在<code>@implementation··@end</code>中,對(duì)象方法的聲明只能寫在<code>@interface···@end</code>中間;對(duì)象方法都以-號(hào)開頭,類方法都以+號(hào)開頭;函數(shù)屬于整個(gè)文件,可以寫在文件中的任何位置,包括<code>@interface··@end</code>中,但寫在<code>@interface···@end</code>會(huì)無法識(shí)別;

對(duì)象方法只能由對(duì)象來調(diào)用,類方法只能由類來調(diào)用,不能當(dāng)做函數(shù)一樣調(diào)用,對(duì)象方法歸類\\對(duì)象所有;類方法調(diào)用不依賴于對(duì)象;類方法內(nèi)部不能直接通過成員變量名訪問對(duì)象的成員變量。OC只支持單繼承,沒有接口,但可以用delegate代替。

Objective-C與其他語(yǔ)言最大的區(qū)別是其運(yùn)行時(shí)的動(dòng)態(tài)性,它能讓你在運(yùn)行時(shí)為類添加方法或者去除方法以及使用反射。極大的方便了程序的擴(kuò)展。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,629評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評(píng)論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,411評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,641評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,820評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,362評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,604評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 在面試中,我們經(jīng)常會(huì)遇到一些原理性的問題,很常識(shí)但很難用通俗的語(yǔ)言解釋清楚,這也是大部分業(yè)務(wù)級(jí)程序員經(jīng)常失誤的地方...
    歐巴冰冰閱讀 1,947評(píng)論 2 21
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,511評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,131評(píng)論 1 32
  • OC語(yǔ)言基礎(chǔ) 1.類與對(duì)象 類方法 OC的類方法只有2種:靜態(tài)方法和實(shí)例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補(bǔ)閱讀 4,312評(píng)論 0 11
  • 這些要好的同學(xué)既是班級(jí)管理的成員,成績(jī)也相對(duì)優(yōu)秀,所以共同話題很多,自然能玩得開。通常就是下課的時(shí)候聊天,聊一聊哪...
    哇_哦閱讀 351評(píng)論 4 6