本文包括:c++
OC的面向對象數組
運行時Runtimexcode
運行循環RunLoop安全
事件響應鏈數據結構
引用計數多線程
生命週期app
與其餘語言的區別框架
Objective-C 簡稱OC(下面以此代稱),是在C語言的基礎上,增長了一層最小的面嚮對象語言。是一種靜態輸入的語言,即「必須先聲明數據中每一個變量(或者容器)的數據類型」。但它是一個動態語言,代碼中的某一部分能夠在app運行的時候被擴展和修改(好比,在被編譯以後)。OC徹底兼容C語言,在代碼中,能夠混用c,甚至是c++代碼。ide
面向對象三原則(封裝,繼承,多態)函數
面向對象具備四個基本特徵:抽象,封裝,繼承和多態。
C語言是面向過程的語言(關注的是函數),OC,C++,JAVA,C#,PHP,Swift是面向對象的,面向過程關注的是解決問題涉及的步驟,而面向對象關注的是設計可以實現解決問題所需功能的類。抽象是面向對象的思想基礎。
抽象包括兩個方面,一是過程抽象,二是數據抽象。過程抽象是指任何一個明肯定義功能的操做均可被使用者看做單個的實體看待,儘管這個操做實際上可能由一系列更低級的操做來完成。數據抽象定義了數據類型和施加於該類型對象上的操做,並限定了對象的值只能經過使用這些操做修改和觀察。抽象是一種思想,封裝繼承和多態是這種思想的實現。
封裝
封裝是把過程和數據包圍起來(即函數和數據結構,函數是行爲,數據結構是描述),有限制的對數據的訪問。面向對象基於這個基本概念開始的(由於面向對象更注重的是類),即現實世界能夠被描繪成一系列徹底自治、封裝的對象,這些對象經過一個受保護的接口訪問其餘對象。一旦定義了一個對象的特性,則有必要決定這些特性的可見性,封裝保證了模塊具備較好的獨立性,使得程序維護修改較爲容易。對應用程序的修改僅限於類的內部,於是能夠將應用程序修改帶來的影響減小到最低限度。可是封裝會致使並行效率問題,由於執行部分和數據部分被綁定在一塊兒,制約了並行程度。面向對象思想將函數和數據綁在一塊兒,擴大了代碼重用時的粒度。並且封裝下的拆箱裝箱過程當中也會致使內存的浪費。
繼承
繼承是一種層次模型,容許和鼓勵類的重用,並提供了一種明確表述共性的方法。新類繼承了原始類的特性,新類稱爲原始類的派生類(子類和父類)。派生類能夠從它的基類那裏繼承方法和實例變量,而且類能夠修改或增長新的方法使之更適合特殊的須要。繼承性很好的解決了軟件的可重用性問題。可是,不恰當地使用繼承致使的最大的一個缺陷特徵就是高耦合(即「牽一髮而動全身」,是設計類時層次沒分清致使的)。解決方案是用組合替代繼承。將模塊拆開,而後經過定義好的接口進行交互,通常來講能夠選擇Delegate模式來交互。使用繼承實際上是如何給一類對象劃分層次的問題。在正確的繼承方式中,父類應當扮演的是底層的角色,子類是上層的業務。父類只是給子類提供服務,並不涉及子類的業務邏輯;層級關係明顯,功能劃分清晰;父類的全部變化,都須要在子類中體現,此時耦合已經成爲需求。
多態
多態性是指容許不一樣類的對象對同一消息做出響應。多態性包括參數化多態性和包含多態性。很好的解決了應用程序函數同名問題,多態通常都要跟繼承結合起來講,其本質是子類經過覆蓋或重載父類的方法,來使得對同一類對象同一方法的調用產生不一樣的結果。覆蓋是對接口方法的實現,繼承中也可能會在子類覆蓋父類中的方法。重載,是指咱們能夠定義一些名稱相同的方法,經過定義不一樣的輸入參數來區分這些方法,而後再調用時,VM就會根據不一樣的參數樣式,來選擇合適的方法執行。在使用重載時只能經過不一樣的參數樣式。例如,不一樣的參數類型,不一樣的參數個數,不一樣的參數順序(固然,同一方法內的幾個參數類型必須不同); 但繼承會引入多態使用混亂的境況併產生耦合,更好的方法是使用接口。經過IOP將子類與可能被子類引入的不相關邏輯剝離開來,提升了子類的可重用性,下降了遷移時可能的耦合。接口規範了子類哪些必須實現,哪些可選實現。那些不在接口定義的方法列表裏的父類方法,事實上就是不建議覆重的方法。若是引入多態以後致使對象角色不夠單純,那就不該當引入多態,若是引入多態以後依舊是單純角色,那就能夠引入多態;若是要覆重的方法是角色業務的其中一個組成部分,那麼就最好不要用多態的方案,用IOP,由於在外界調用的時候其實並不須要經過多態來知足定製化的需求。
動態性(Runtime)
Objective-C 是面相運行時的語言,它會盡量的把編譯和連接時要執行的邏輯延遲到運行時。使用Runtime能夠按須要把消息重定向給合適的對象,交換方法的實現等等。
Runtime簡稱運行時,其中最主要的是消息機制,是一個主要使用 C 和彙編寫的庫,爲 C 添加了面相對象的能力並創造了 Objective-C。。OC的函數調用稱爲消息發送。屬於動態調用過程。在編譯的時候並不能決定真正調用哪一個函數(在編 譯階段,OC能夠調用任何函數,即便這個函數並未實現,只要聲明過就不會報錯。而C語言在編譯階段就會報錯)。只有在真正運行的時候纔會根據函數的名稱找 到對應的函數來調用。
如:
1 2 3 |
|
編譯器執行上述轉換。在objc_msgSend函數中,首先經過obj的isa指針找到obj對應的class。每一個對象內部都默認有一個isa指針指向這個對象所使用的類。isa是對象中的隱藏指針,指向建立這個對象的類。在Class中先去cache中經過SEL查找對應函數method(cache中method列表是以SEL爲key經過hash表來存儲的,這樣能提升函數查找速度),若cache中未找到,再去methodList中查找,若methodlist中未找到,則取superClass中查找。若能找到,則將method加入到cache中,以方便下次查找,並經過method中的函數指針跳轉到對應的函數中去執行。
動態性的三方面
OC的動態特性表現爲了三個方面:動態類型、動態綁定、動態加載。之因此叫作動態,是由於必須到運行時(runtime)纔會作一些事情。
動態類型,就是id類型。動態類型是跟靜態類型相對的。內置的基本類型都屬於靜態類型(int、NSString等)。靜態類型在編譯的時候就能被識別出來(即前面說的靜態輸入)。因此,若程序發生了類型不對應,編譯器就會發出警告。而動態類型就編譯器編譯的時候是不能被識別的,要等到運行時(runtime),即程序運行的時候纔會根據語境來識別。因此這裏面就有兩個概念要分清:編譯時跟運行時。
動態語言和靜態語言的一個區別是靜態語言提早編譯好文件,即全部的邏輯已在編譯時肯定,運行時直接加載編譯後的文件;而動態語言是在運行時才肯定實現。典型的靜態語言是C++,動態語言包括OC,JAVA,C#等;由於靜態語言提早編譯好了執行文件,也就是一般所說的靜態語言效率較高的緣由。
動態綁定(dynamic binding)須要用到@selector/SEL。先來看看「函數」,對於其餘一些靜態語言,好比c++,通常在編譯的時候就已經將要調用的函數的函數簽名都告訴編譯器了。靜態的,不能改變。而在OC中,實際上是沒有函數的概念的,咱們叫「消息機制」,所謂的函數調用就是給對象發送一條消息。這時,動態綁定的特性就來了。OC能夠先跳過編譯,到運行的時候才動態地添加函數調用,在運行時才決定要調用什麼方法,須要傳什麼參數進去,這就是動態綁定。要實現他就必須用SEL變量綁定一個方法。最終造成的這個SEL變量就表明一個方法的引用。這裏要注意一點:SEL並非C裏面的函數指針,雖然很像,但真心不是函數指針。SEL變量只是一個整數,他是該方法的ID。之前的函數調用,是根據函數名,也就是字符串去查找函數體。但如今,咱們是根據一個ID整數來查找方法,整數的查找天然要比字符串的查找快得多!因此,動態綁定的特定不只方便,並且效率更高。
動態加載就是根據需求動態地加載資源,在運行時加載新類。在運行時建立一個新類,只須要3步:
一、爲 class pair分配存儲空間 ,使用 objc_allocateClassPair函數
二、增長鬚要的方法使用class_addMethod函數,增長實例變量用class_addIvar
3 、用objc_registerClassPair函數註冊這個類,以便它能被別人使用。
Method Swizzling
在Objective-C中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector的名字。利用Objective-C的動態特性,能夠實如今運行時偷換selector對應的方法實現,達到給方法掛鉤的目的。每一個類都有一個方法列表,存放着selector的名字和方法實現的映射關係。IMP相似函數指針,指向具體的Method實現。
用 method_exchangeImplementations 來交換2個方法中的IMP,
用 class_replaceMethod 來修改類,
用 method_setImplementation 來直接設置某個方法的IMP,歸根結底,都是偷換了selector的IMP。
RunLoop
RunLoop是一讓線程能隨時處理事件但不退出的機制。RunLoop其實是一個對象,這個對象管理了其須要處理的事件和消息,並提供了一個入口函數來執行Event Loop的邏輯。線程執行了這個函數後,就會一直處於這個函數內部 "接受消息->等待->處理" 的循環中,直到這個循環結束(好比傳入 quit 的消息),函數返回。讓線程在沒有處理消息時休眠以免資源佔用、在有消息到來時馬上被喚醒。一個runloop就是一個事件處理循環,用來不停的監聽和處理輸入事件並將其分配到對應的目標上進行處理。
RunLoop的四個做用爲:使程序一直運行接受用戶輸入;決定程序在什麼時候應該處理哪些Event;調用解耦;節省CPU時間。
線程和 RunLoop 之間是一一對應的,其關係是保存在一個全局的 Dictionary 裏。線程剛建立時並無 RunLoop,若是你不主動獲取,那它一直都不會有。RunLoop 的建立是發生在第一次獲取時,RunLoop 的銷燬是發生在線程結束時。你只能在一個線程的內部獲取其RunLoop(主線程除外)。
主線程的runloop默認是啓動的。
OSX/iOS 系統中,提供了兩個這樣的對象:NSRunLoop和CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架內的,它提供了純 C 函數的 API,全部這些 API 都是線程安全的。NSRunLoop 是基於 CFRunLoopRef 的封裝,提供了面向對象的 API,可是這些 API 不是線程安全的。
NSRunLoop是一種更加高明的消息處理模式,在對消息處理過程進行了更好的抽象和封裝,不用處理一些很瑣碎很低層次的具體消息的處理,在NSRunLoop中每個消息就被打包在input source或者是timer source中了。使用run loop可使你的線程在有工做的時候工做,沒有工做的時候休眠,能夠大大節省系統資源。
對其它線程來講,runloop默認是沒有啓動的,若是你須要更多的線程交互則能夠手動配置和啓動,若是線程只是去執行一個長時間的已肯定的任務則不須要。在任何一個Cocoa程序的線程中,均可以經過:
1 |
|
獲取到當前線程的runloop。
Cocoa中的NSRunLoop類並非線程安全的
咱們不能在一個線程中去操做另一個線程的runloop對象,那極可能會形成意想不到的後果。可是CoreFundation中的不透明類CFRunLoopRef是線程安全的,並且兩種類型的runloop徹底能夠混合使用。Cocoa中的NSRunLoop類能夠經過實例方法:
1 |
|
獲取對應的CFRunLoopRef類,來達到線程安全的目的。
Runloop的管理並不徹底是自動的。咱們仍必須設計線程代碼以在適當的時候啓動runloop並正確響應輸入事件,固然前提是線程中須要用到runloop。並且,咱們還須要使用while/for語句來驅動runloop可以循環運行,下面的代碼就成功驅動了一個run loop:
1 2 3 4 |
|
Runloop同時也負責autorelease pool的建立和釋放
在使用手動的內存管理方式的項目中,會常常用到不少自動釋放的對象,若是這些對象不可以被即時釋放掉,會形成內存佔用量急劇增大。Runloop就爲咱們作了這樣的工做,每當一個運行循環結束的時候,它都會釋放一次autorelease pool,同時pool中的全部自動釋放類型變量都會被釋放掉。
系統默認註冊了5個Mode:
kCFRunLoopDefaultMode: App的默認 Mode,一般主線程是在這個 Mode 下運行的。
UITrackingRunLoopMode: 界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響。
UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就再也不使用。
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,一般用不到。
kCFRunLoopCommonModes: 這是一個佔位的 Mode,沒有實際做用。
輪播圖中的NSTimer問題
建立定時器:
1 |
|
此方法建立的定時器,必須加到NSRunLoop中。
1 2 |
|
forMode的參數有兩種類型可供選擇: NSDefaultRunLoopMode , NSRunLoopCommonModes,第一個參數爲默認參數,當下面有textView,textfield等控件時,拖拽控件,此時輪播器會中止輪播,是由於NSRunLoop的緣由,NSRunLoop爲一個死循環,實時監測有無事件響應,若是當前線程就是主線程,也就是UI線程時,某些UI事件,好比UIScrollView的拖動操做,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程當中,默認的NSDefaultRunLoopMode模式中註冊的事件是不會被執行的。NSRunLoopCommonModes 可以在多線程中起做用,這個模式等效於NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結合,這也是將modes換爲NSRunLoopCommonModes即可解決的緣由。
1 |
|
此種建立定時器的方式,默認加到了runloop,且默認爲第二個參數。
main函數的運行
在main.m中:
1 2 3 4 5 6 |
|
UIApplicationMain() 函數會爲main thread 設置一個NSRunLoop 對象,這就解釋了app應用能夠在無人操做的時候休息,須要讓它幹活的時候又能立馬響應。
僅當在爲你的程序建立輔助線程的時候,你才須要顯式運行一個runloop。Runloop是程序主線程基礎設施的關鍵部分,因此,Cocoa和Carbon程序提供了代碼運行主程序的循環並自動啓動runloop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)做爲程序啓動步驟的一部分,它在程序正常啓動的時候就會啓動程序的主循環。若是你使用xcode提供的模板建立你的程序,那你永遠不須要本身去顯式的調用這些例程。
對於輔助線程,你須要判斷一個runloop是不是必須的。若是是必須的,那麼你要本身配置並啓動它。你不須要在任何狀況下都去啓動一個線程的runloop。好比,你使用線程來處理一個預先定義的長時間運行的任務時,你應該避免啓動runloop。Runloop在你要和線程有更多的交互時才須要,好比如下狀況:
1.使用端口或自定義輸入源來和其餘線程通訊;
2.使用線程的定時器;
3.Cocoa中使用任何performSelector...的方法;
4.使線程週期性工做;
事件響應鏈
對於IOS設備用戶來講,操做設備的方式主要有三種:觸摸屏幕、晃動設備、經過遙控設施控制設備。對應的事件類型有如下三種:
一、觸屏事件(Touch Event)
二、運動事件(Motion Event)
三、遠端控制事件(Remote-Control Event)
事件的傳遞和響應分兩個鏈:
傳遞鏈:由系統向離用戶最近的view傳遞。
UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
響應鏈:由離用戶最近的view向系統傳遞。
initial view –> super view –> …..–> view controller –> window –> Application
響應者鏈(Responder Chain):由多個響應者對象鏈接起來的鏈條,做用是能很清楚的看見每一個響應者之間的聯繫,而且可讓一個事件多個對象處理。
響應者對象(Responder Object),指的是有響應和處理事件能力的對象。響應者鏈就是由一系列的響應者對象構成的一個層次結構。
UIResponder是全部響應對象的基類,在UIResponder類中定義了處理上述各類事件的接口。咱們熟悉的UIApplication、 UIViewController、UIWindow和全部繼承自UIView的UIKit類都直接或間接的繼承自UIResponder,因此它們的實例都是能夠構成響應者鏈的響應者對象。
響應者鏈有如下特色:
一、響應者鏈一般是由視圖(UIView)構成的;
二、一個視圖的下一個響應者是它視圖控制器(UIViewController)(若是有的話),而後再轉給它的父視圖(Super View);
三、視圖控制器(若是有的話)的下一個響應者爲其管理的視圖的父視圖;
四、單例的窗口(UIWindow)的內容視圖將指向窗口自己做爲它的下一個響應者,Cocoa Touch應用不像Cocoa應用,它只有一個UIWindow對象,所以整個響應者鏈要簡單一點;
五、單例的應用(UIApplication)是一個響應者鏈的終點,它的下一個響應者指向nil,以結束整個循環。
iOS系統檢測到手指觸摸(Touch)操做時會將其打包成一個UIEvent對象,並放入當前活動Application的事件隊列,單例的UIApplication會從事件隊列中取出觸摸事件並傳遞給單例的UIWindow來處理,UIWindow對象首先會使用hitTest:withEvent:方法尋找這次Touch操做初始點所在的視圖(View),即須要將觸摸事件傳遞給其處理的視圖,這個過程稱之爲hit-test view。
UIWindow實例對象會首先在它的內容視圖上調用hitTest:withEvent:,此方法會在其視圖層級結構中的每一個視圖上調用pointInside:withEvent:(該方法用來判斷點擊事件發生的位置是否處於當前視圖範圍內,以肯定用戶是否是點擊了當前視圖),若是pointInside:withEvent:返回YES,則繼續逐級調用,直到找到touch操做發生的位置,這個視圖也就是要找的hit-test view。
hitTest:withEvent:方法的處理流程以下:
首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;若返回NO,則hitTest:withEvent:返回nil;若返回YES,則向當前視圖的全部子視圖(subviews)發送hitTest:withEvent:消息,全部子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者所有子視圖遍歷完畢;若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象,處理結束;如全部子視圖都返回非,則hitTest:withEvent:方法返回自身(self)。
引用計數器(ARC 和 MRC)
ARC:自動引用計數器(Automatic Reference Counting)
MRC:手動引用計算器(因爲如今幾乎不用了,不作過多解說)
Objective-C中提供了兩種內存管理機制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分別提供對內存的手動和自動管理,來知足不一樣的需求。Xcode 4.1及其之前版本沒有ARC。
在MRC的內存管理模式下,與對變量的管理相關的方法有:retain,release和autorelease。retain和release方法操做的是引用記數,當引用記數爲零時,便自動釋放內存。而且能夠用NSAutoreleasePool對象,對加入自動釋放池(autorelease調用)的變量進行管理,當內存緊張時回收內存。
(1) retain,該方法的做用是將內存數據的全部權附給另外一指針變量,引用數加1,即retainCount+= 1;
(2) release,該方法是釋放指針變量對內存數據的全部權,引用數減1,即retainCount-= 1;
(3) autorelease,該方法是將該對象內存的管理放到autoreleasepool中。
在ARC中與內存管理有關的標識符,能夠分爲變量標識符和屬性標識符,對於變量默認爲__strong,而對於屬性默認爲unsafe_unretained。也存在autoreleasepool。
其中assign/retain/copy與MRC下property的標識符意義相同,strong相似與retain,assign相似於unsafe_unretained,strong/weak/unsafe_unretained與ARC下變量標識符意義相同,只是一個用於屬性的標識,一個用於變量的標識(帶兩個下劃短線__)。
生命週期
app應用程序有5種狀態:
Not running未運行:程序沒啓動。
Inactive未激活:程序在前臺運行,不過沒有接收到事件。在沒有事件處理狀況下程序一般停留在這個狀態。
Active激活:程序在前臺運行並且接收到了事件。這也是前臺的一個正常的模式。
Backgroud後臺:程序在後臺並且能執行代碼,大多數程序進入這個狀態後會在在這個狀態上停留一會。時間到以後會進入掛起狀態(Suspended)。有的程序通過特殊的請求後能夠長期處於Backgroud狀態。
Suspended掛起:程序在後臺不能執行代碼。系統會自動把程序變成這個狀態並且不會發出通知。當掛起時,程序仍是停留在內存中的,當系統內存低時,系統就把掛起的程序清除掉,爲前臺程序提供更多的內存。
iOS的入口在main.m文件的main函數,根據UIApplicationMain函數,程序將進入AppDelegate.m,這個文件是xcode新建工程時自動生成的。AppDelegate.m文件,關乎着應用程序的生命週期。
一、application didFinishLaunchingWithOptions:當應用程序啓動時執行,應用程序啓動入口,只在應用程序啓動時執行一次。若用戶直接啓動,lauchOptions內無數據,若經過其餘方式啓動應用,lauchOptions包含對應方式的內容。
二、applicationWillResignActive:在應用程序將要由活動狀態切換到非活動狀態時候,要執行的委託調用,如 按下 home 按鈕,返回主屏幕,或全屏之間切換應用程序等。
三、applicationDidEnterBackground:在應用程序已進入後臺程序時,要執行的委託調用。
四、applicationWillEnterForeground:在應用程序將要進入前臺時(被激活),要執行的委託調用,恰好與applicationWillResignActive 方法相對應。
五、applicationDidBecomeActive:在應用程序已被激活後,要執行的委託調用,恰好與applicationDidEnterBackground 方法相對應。
六、applicationWillTerminate:在應用程序要徹底推出的時候,要執行的委託調用,這個須要要設置UIApplicationExitsOnSuspend的鍵值。
初次啓動:
iOS_didFinishLaunchingWithOptions
iOS_applicationDidBecomeActive
按下home鍵:
iOS_applicationWillResignActive
iOS_applicationDidEnterBackground
點擊程序圖標進入:
iOS_applicationWillEnterForeground
iOS_applicationDidBecomeActive
當應用程序進入後臺時,應該保存用戶數據或狀態信息,全部沒寫到磁盤的文件或信息,在進入後臺時,最後都寫到磁盤去,由於程序可能在後臺被殺死。釋放盡量釋放的內存。
1 |
|
方法有大概5秒的時間讓你完成這些任務。若是超過期間還有未完成的任務,你的程序就會被終止並且從內存中清除。
若是還須要長時間的運行任務,能夠在該方法中調用
1 2 3 |
|
程序終止
程序只要符合如下狀況之一,只要進入後臺或掛起狀態就會終止:
①iOS4.0之前的系統
②app是基於iOS4.0以前系統開發的。
③設備不支持多任務
④在Info.plist文件中,程序包含了 UIApplicationExitsOnSuspend 鍵。
系統經常是爲其餘app啓動時因爲內存不足而回收內存最後須要終止應用程序,但有時也會是因爲app很長時間才響應而終止。若是app當時運行在後臺而且沒有暫停,系統會在應用程序終止以前調用app的代理的方法 - (void)applicationWillTerminate:(UIApplication *)application,這樣可讓你能夠作一些清理工做。你能夠保存一些數據或app的狀態。這個方法也有5秒鐘的限制。超時後方法會返回程序從內存中清除。用戶能夠手工關閉應用程序。
和其餘動態語言的區別
OC中方法的實現只能寫在@implementation··@end中,對象方法的聲明只能寫在@interface···@end中間;對象方法都以-號開頭,類方法都以+號開頭;函數屬於整個文件,能夠寫在文件中的任何位置,包括@interface··@end中,但寫在@interface···@end會沒法識別;
對象方法只能由對象來調用,類方法只能由類來調用,不能當作函數同樣調用,對象方法歸類\\對象全部;類方法調用不依賴於對象;類方法內部不能直接經過成員變量名訪問對象的成員變量。OC只支持單繼承,沒有接口,但能夠用delegate代替。
Objective-C與其餘語言最大的區別是其運行時的動態性,它能讓你在運行時爲類添加方法或者去除方法以及使用反射。極大的方便了程序的擴展。