iOS Runloop 與Runtime

Runloop

做了一年多的IOS開(kāi)發(fā)豫尽,對(duì)IOS和Objective-C深層次的了解還十分有限炭晒,大多還停留在會(huì)用API的級(jí)別执庐,這是件挺可悲的事情贰镣。想學(xué)好一門(mén)語(yǔ)言

還是需要深層次的了解它榴啸,這樣才能在使用的時(shí)候得心應(yīng)手孽惰,出現(xiàn)各種怪異的問(wèn)題時(shí)不至于不知所措。廢話少說(shuō)鸥印,進(jìn)入今天的正題灰瞻。

不知道大家有沒(méi)有想過(guò)這個(gè)問(wèn)題,一個(gè)應(yīng)用開(kāi)始運(yùn)行以后放在那里辅甥,如果不對(duì)它進(jìn)行任何操作酝润,這個(gè)應(yīng)用就像靜止了一樣,不會(huì)自發(fā)的有任何動(dòng)作發(fā)生璃弄,但是如果我

們點(diǎn)擊界面上的一個(gè)按鈕要销,這個(gè)時(shí)候就會(huì)有對(duì)應(yīng)的按鈕響應(yīng)事件發(fā)生。給我們的感覺(jué)就像應(yīng)用一直處于隨時(shí)待命的狀態(tài)夏块,在沒(méi)人操作的時(shí)候它一直在休息疏咐,在讓它干

活的時(shí)候纤掸,它就能立刻響應(yīng)。其實(shí)浑塞,這就是run loop的功勞借跪。

一、線程與run loop

1.1 線程任務(wù)的類(lèi)型

再來(lái)說(shuō)說(shuō)線程酌壕。有些線程執(zhí)行的任務(wù)是一條直線掏愁,起點(diǎn)到終點(diǎn);而另一些線程要干的活則是一個(gè)圓卵牍,不斷循環(huán)果港,直到通過(guò)某種方式將它終止。直線線程如簡(jiǎn)單的

Hello

World糊昙,運(yùn)行打印完,它的生命周期便結(jié)束了辛掠,像曇花一現(xiàn)那樣;圓類(lèi)型的如操作系統(tǒng)释牺,一直運(yùn)行直到你關(guān)機(jī)萝衩。在IOS中,圓型的線程就是通過(guò)run

loop不停的循環(huán)實(shí)現(xiàn)的没咙。

1.2 線程與run loop的關(guān)系

Run loop猩谊,正如其名,loop表示某種循環(huán)镜撩,和run放在一起就表示一直在運(yùn)行著的循環(huán)预柒。實(shí)際上,run

loop和線程是緊密相連的袁梗,可以這樣說(shuō)run loop是為了線程而生宜鸯,沒(méi)有線程,它就沒(méi)有存在的必要遮怜。Run

loops是線程的基礎(chǔ)架構(gòu)部分淋袖,Cocoa和CoreFundation都提供了run loop對(duì)象方便配置和管理線程的run

loop(以下都已Cocoa為例)。每個(gè)線程锯梁,包括程序的主線程(main thread)都有與之相應(yīng)的run loop對(duì)象即碗。

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

iOS的應(yīng)用程序里面陌凳,程序啟動(dòng)后會(huì)有一個(gè)如下的main() 函數(shù):

intmain(intargc,char*argv[])

{

@autoreleasepool{

returnUIApplicationMain(argc, argv,nil,NSStringFromClass([appDelegateclass]));

}

}

重點(diǎn)是UIApplicationMain() 函數(shù)剥懒,這個(gè)方法會(huì)為main thread 設(shè)置一個(gè)NSRunLoop 對(duì)象,這就解釋了本文開(kāi)始說(shuō)的為什么我們的應(yīng)用可以在無(wú)人操作的時(shí)候休息合敦,需要讓它干活的時(shí)候又能立馬響應(yīng)初橘。

1.2.2 對(duì)其它線程來(lái)說(shuō),run loop默認(rèn)是沒(méi)有啟動(dòng)的,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng)保檐,如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要耕蝉。

1.2.3 在任何一個(gè)Cocoa程序的線程中,都可以通過(guò):

NSRunLoop*runloop = [NSRunLoopcurrentRunLoop];

來(lái)獲取到當(dāng)前線程的run loop夜只。

1.3 關(guān)于run loop的幾點(diǎn)說(shuō)明

1.3.1 Cocoa中的NSRunLoop類(lèi)并不是線程安全的

我們不能再一個(gè)線程中去操作另外一個(gè)線程的run

loop對(duì)象垒在,那很可能會(huì)造成意想不到的后果。不過(guò)幸運(yùn)的是CoreFundation中的不透明類(lèi)CFRunLoopRef是線程安全的扔亥,而且兩種類(lèi)型

的run loop完全可以混合使用场躯。Cocoa中的NSRunLoop類(lèi)可以通過(guò)實(shí)例方法:

  • (CFRunLoopRef)getCFRunLoop;

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

1.3.2 Run loop的管理并不完全是自動(dòng)的推盛。

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

BOOLisRunning =NO;

do{

isRunning = [[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];

}while(isRunning);

1.3.3 Run loop同時(shí)也負(fù)責(zé)autorelease pool的創(chuàng)建和釋放

在使用手動(dòng)的內(nèi)存管理方式的項(xiàng)目中驹闰,會(huì)經(jīng)常用到很多自動(dòng)釋放的對(duì)象,如果這些對(duì)象不能夠被即時(shí)釋放掉撒会,會(huì)造成內(nèi)存占用量急劇增大嘹朗。Run

loop就為我們做了這樣的工作,每當(dāng)一個(gè)運(yùn)行循環(huán)結(jié)束的時(shí)候诵肛,它都會(huì)釋放一次autorelease

pool屹培,同時(shí)pool中的所有自動(dòng)釋放類(lèi)型變量都會(huì)被釋放掉。

1.3.4 Run loop的優(yōu)點(diǎn)

一個(gè)run

loop就是一個(gè)事件處理循環(huán)怔檩,用來(lái)不停的監(jiān)聽(tīng)和處理輸入事件并將其分配到對(duì)應(yīng)的目標(biāo)上進(jìn)行處理褪秀。如果僅僅是想實(shí)現(xiàn)這個(gè)功能,你可能會(huì)想一個(gè)簡(jiǎn)單的

while循環(huán)不就可以實(shí)現(xiàn)了嗎薛训,用得著費(fèi)老大勁來(lái)做個(gè)那么復(fù)雜的機(jī)制媒吗?顯然,蘋(píng)果的架構(gòu)設(shè)計(jì)師不是吃干飯的乙埃,你想到的他們?cè)缇拖脒^(guò)了闸英。

首先,NSRunLoop是一種更加高明的消息處理模式介袜,他就高明在對(duì)消息處理過(guò)程進(jìn)行了更好的抽象和封裝甫何,這樣才能是的你不用處理一些很瑣碎很低層次的

具體消息的處理,在NSRunLoop中每一個(gè)消息就被打包在input source或者是timer source(見(jiàn)后文)中了遇伞。

其次辙喂,也是很重要的一點(diǎn),使用run loop可以使你的線程在有工作的時(shí)候工作,沒(méi)有工作的時(shí)候休眠加派,這可以大大節(jié)省系統(tǒng)資源叫确。

二、Run loop相關(guān)知識(shí)點(diǎn)

2.1輸入事件來(lái)源

Run loop接收輸入事件來(lái)自?xún)煞N不同的來(lái)源:輸入源(input source)和定時(shí)源(timer source)芍锦。兩種源都使用程序的某一特定的處理例程來(lái)處理到達(dá)的事件竹勉。圖-1顯示了run loop的概念結(jié)構(gòu)以及各種源。

需要說(shuō)明的是娄琉,當(dāng)你創(chuàng)建輸入源次乓,你需要將其分配給run loop中的一個(gè)或多個(gè)模式(什么是模式,下文將會(huì)講到)孽水。模式只會(huì)在特定事件影響監(jiān)聽(tīng)的源票腰。大多數(shù)情況下,run loop運(yùn)行在默認(rèn)模式下女气,但是你也可以使其運(yùn)行在自定義模式杏慰。若某一源在當(dāng)前模式下不被監(jiān)聽(tīng),那么任何其生成的消息只在run loop運(yùn)行在其關(guān)聯(lián)的模式下才會(huì)被傳遞炼鞠。

圖-1 Runloop的結(jié)構(gòu)和輸入源類(lèi)型

image

2.1.1輸入源(input source)

傳遞異步事件缘滥,通常消息來(lái)自于其他線程或程序。輸入源傳遞異步消息給相應(yīng)的處理例程谒主,并調(diào)用runUntilDate:方法來(lái)退出(在線程里面相關(guān)的NSRunLoop對(duì)象調(diào)用)朝扼。

2.1.1.1基于端口的輸入源

基于端口的輸入源由內(nèi)核自動(dòng)發(fā)送。

Cocoa和Core

Foundation內(nèi)置支持使用端口相關(guān)的對(duì)象和函數(shù)來(lái)創(chuàng)建的基于端口的源霎肯。例如擎颖,在Cocoa里面你從來(lái)不需要直接創(chuàng)建輸入源。你只要簡(jiǎn)單的創(chuàng)建端口

對(duì)象观游,并使用NSPort的方法把該端口添加到run loop搂捧。端口對(duì)象會(huì)自己處理創(chuàng)建和配置輸入源。

在Core Foundation备典,你必須人工創(chuàng)建端口和它的run

loop源异旧。我們可以使用端口相關(guān)的函數(shù)(CFMachPortRef,CFMessagePortRef提佣,CFSocketRef)來(lái)創(chuàng)建合適的對(duì)象吮蛹。

下面的例子展示了如何創(chuàng)建一個(gè)基于端口的輸入源,將其添加到run loop并啟動(dòng):

voidcreatePortSource()

{

CFMessagePortRefport =CFMessagePortCreateLocal(kCFAllocatorDefault,CFSTR("com.someport"),myCallbackFunc,NULL,NULL);

CFRunLoopSourceRefsource =CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port,0);

CFRunLoopAddSource(CFRunLoopGetCurrent(), source,kCFRunLoopCommonModes);

while(pageStillLoading) {

NSAutoreleasePool*pool = [[NSAutoreleasePoolalloc]init];

CFRunLoopRun();

[poolrelease];

}

CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source,kCFRunLoopDefaultMode);

CFRelease(source);

}

2.1.1.2自定義輸入源

自定義的輸入源需要人工從其他線程發(fā)送拌屏。

為了創(chuàng)建自定義輸入源潮针,必須使用Core

Foundation里面的CFRunLoopSourceRef類(lèi)型相關(guān)的函數(shù)來(lái)創(chuàng)建。你可以使用回調(diào)函數(shù)來(lái)配置自定義輸入源倚喂。Core

Fundation會(huì)在配置源的不同地方調(diào)用回調(diào)函數(shù)每篷,處理輸入事件瓣戚,在源從run loop移除的時(shí)候清理它。

除了定義在事件到達(dá)時(shí)自定義輸入源的行為焦读,你也必須定義消息傳遞機(jī)制子库。源的這部分運(yùn)行在單獨(dú)的線程里面,并負(fù)責(zé)在數(shù)據(jù)等待處理的時(shí)候傳遞數(shù)據(jù)給源并通知它處理數(shù)據(jù)矗晃。消息傳遞機(jī)制的定義取決于你仑嗅,但最好不要過(guò)于復(fù)雜。創(chuàng)建并啟動(dòng)自定義輸入源的示例如下:

voidcreateCustomSource()

{

CFRunLoopSourceContextcontext = {0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};

CFRunLoopSourceRefsource =CFRunLoopSourceCreate(kCFAllocatorDefault,0, &context);

CFRunLoopAddSource(CFRunLoopGetCurrent(), source,kCFRunLoopDefaultMode);

while(pageStillLoading) {

NSAutoreleasePool*pool = [[NSAutoreleasePoolalloc]init];

CFRunLoopRun();

[poolrelease];

}

CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source,kCFRunLoopDefaultMode);

CFRelease(source);

}

2.1.1.3Cocoa上的Selector源

除了基于端口的源张症,Cocoa定義了自定義輸入源仓技,允許你在任何線程執(zhí)行selector方法。和基于端口的源一樣俗他,執(zhí)行selector請(qǐng)求會(huì)在目標(biāo)線

程上序列化脖捻,減緩許多在線程上允許多個(gè)方法容易引起的同步問(wèn)題。不像基于端口的源兆衅,一個(gè)selector執(zhí)行完后會(huì)自動(dòng)從run loop里面移除地沮。

當(dāng)在其他線程上面執(zhí)行selector時(shí),目標(biāo)線程須有一個(gè)活動(dòng)的run loop涯保。對(duì)于你創(chuàng)建的線程诉濒,這意味著線程在你顯式的啟動(dòng)run loop之前是不會(huì)執(zhí)行selector方法的周伦,而是一直處于休眠狀態(tài)夕春。

NSObject類(lèi)提供了類(lèi)似如下的selector方法:

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray*)array;

2.1.2定時(shí)源(timer source)

定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)同步方式傳遞消息,這些消息都會(huì)發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔专挪。定時(shí)源則直接傳遞消息給處理例程及志,不會(huì)立即退出run loop。

需要注意的是寨腔,盡管定時(shí)器可以產(chǎn)生基于時(shí)間的通知速侈,但它并不是實(shí)時(shí)機(jī)制。和輸入源一樣迫卢,定時(shí)器也和你的run

loop的特定模式相關(guān)倚搬。如果定時(shí)器所在的模式當(dāng)前未被run loop監(jiān)視,那么定時(shí)器將不會(huì)開(kāi)始直到run

loop運(yùn)行在相應(yīng)的模式下乾蛤。類(lèi)似的每界,如果定時(shí)器在run loop處理某一事件期間開(kāi)始,定時(shí)器會(huì)一直等待直到下次run

loop開(kāi)始相應(yīng)的處理程序家卖。如果run loop不再運(yùn)行眨层,那定時(shí)器也將永遠(yuǎn)不啟動(dòng)。

創(chuàng)建定時(shí)器源有兩種方法上荡,

方法一:

NSTimer *timer = [NSTimerscheduledTimerWithTimeInterval:4.0

target:self

selector:@selector(backgroundThreadFire:) userInfo:nil

repeats:YES];

[[NSRunLoop currentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];

方法二:

[NSTimerscheduledTimerWithTimeInterval:10

target:self

selector:@selector(backgroundThreadFire:)

userInfo:nil

repeats:YES];

2.2 RunLoop觀察者

源是在合適的同步或異步事件發(fā)生時(shí)觸發(fā)趴樱,而run loop觀察者則是在run loop本身運(yùn)行的特定時(shí)候觸發(fā)。你可以使用run loop觀察者來(lái)為處理某一特定事件或是進(jìn)入休眠的線程做準(zhǔn)備。你可以將run loop觀察者和以下事件關(guān)聯(lián):

  1. Runloop入口

  2. Runloop何時(shí)處理一個(gè)定時(shí)器

  3. Runloop何時(shí)處理一個(gè)輸入源

  4. Runloop何時(shí)進(jìn)入睡眠狀態(tài)

  5. Runloop何時(shí)被喚醒叁征,但在喚醒之前要處理的事件

  6. Runloop終止

和定時(shí)器類(lèi)似纳账,在創(chuàng)建的時(shí)候你可以指定run loop觀察者可以只用一次或循環(huán)使用。若只用一次捺疼,那么在它啟動(dòng)后塞祈,會(huì)把它自己從run

loop里面移除,而循環(huán)的觀察者則不會(huì)帅涂。定義觀察者并把它添加到run loop议薪,只能使用Core

Fundation。下面的例子演示了如何創(chuàng)建run loop的觀察者:

  • (void)addObserverToCurrentRunloop

{

// The application uses garbage collection, so noautorelease pool is needed.

NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];

// Create a run loop observer and attach it to the runloop.

CFRunLoopObserverContextcontext = {0,self,NULL,NULL,NULL};

CFRunLoopObserverRef observer =CFRunLoopObserverCreate(kCFAllocatorDefault,

kCFRunLoopBeforeTimers,YES,0, &myRunLoopObserver, &context);

if(observer)

{

CFRunLoopRefcfLoop = [myRunLoopgetCFRunLoop];

CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);

}

}

其中媳友,kCFRunLoopBeforeTimers表示選擇監(jiān)聽(tīng)定時(shí)器觸發(fā)前處理事件斯议,后面的YES表示循環(huán)監(jiān)聽(tīng)。

2.3 RunLoop的事件隊(duì)列

每次運(yùn)行run loop醇锚,你線程的run loop對(duì)會(huì)自動(dòng)處理之前未處理的消息哼御,并通知相關(guān)的觀察者。具體的順序如下:

通知觀察者run loop已經(jīng)啟動(dòng)

通知觀察者任何即將要開(kāi)始的定時(shí)器

通知觀察者任何即將啟動(dòng)的非基于端口的源

啟動(dòng)任何準(zhǔn)備好的非基于端口的源

如果基于端口的源準(zhǔn)備好并處于等待狀態(tài)焊唬,立即啟動(dòng)恋昼;并進(jìn)入步驟9。

通知觀察者線程進(jìn)入休眠

將線程置于休眠直到任一下面的事件發(fā)生:

某一事件到達(dá)基于端口的源

定時(shí)器啟動(dòng)

Run loop設(shè)置的時(shí)間已經(jīng)超時(shí)

run loop被顯式喚醒

通知觀察者線程將被喚醒赶促。

處理未處理的事件

如果用戶定義的定時(shí)器啟動(dòng)液肌,處理定時(shí)器事件并重啟run loop。進(jìn)入步驟2

如果輸入源啟動(dòng)鸥滨,傳遞相應(yīng)的消息

如果run loop被顯式喚醒而且時(shí)間還沒(méi)超時(shí)嗦哆,重啟run loop。進(jìn)入步驟2

通知觀察者run loop結(jié)束婿滓。

因?yàn)槎〞r(shí)器和輸入源的觀察者是在相應(yīng)的事件發(fā)生之前傳遞消息老速,所以通知的時(shí)間和實(shí)際事件發(fā)生的時(shí)間之間可能存在誤差。如果需要精確時(shí)間控制凸主,你可以使用休眠和喚醒通知來(lái)幫助你校對(duì)實(shí)際發(fā)生事件的時(shí)間橘券。

因?yàn)楫?dāng)你運(yùn)行run loop時(shí)定時(shí)器和其它周期性事件經(jīng)常需要被傳遞,撤銷(xiāo)run loop也會(huì)終止消息傳遞卿吐。典型的例子就是鼠標(biāo)路徑追蹤旁舰。因?yàn)槟愕拇a直接獲取到消息而不是經(jīng)由程序傳遞,因此活躍的定時(shí)器不會(huì)開(kāi)始直到鼠標(biāo)追蹤結(jié)束并將控制權(quán)交給程序但两。

Run loop可以由run loop對(duì)象顯式喚醒鬓梅。其它消息也可以喚醒run loop。例如谨湘,添加新的非基于端口的源會(huì)喚醒run loop從而可以立即處理輸入源而不需要等待其他事件發(fā)生后再處理绽快。

從這個(gè)事件隊(duì)列中可以看出:

①如果是事件到達(dá)芥丧,消息會(huì)被傳遞給相應(yīng)的處理程序來(lái)處理, runloop處理完當(dāng)次事件后坊罢,run loop會(huì)退出续担,而不管之前預(yù)定的時(shí)間到了沒(méi)有。你可以重新啟動(dòng)run loop來(lái)等待下一事件活孩。

②如果線程中有需要處理的源物遇,但是響應(yīng)的事件沒(méi)有到來(lái)的時(shí)候,線程就會(huì)休眠等待相應(yīng)事件的發(fā)生憾儒。這就是為什么run loop可以做到讓線程有工作的時(shí)候忙于工作询兴,而沒(méi)工作的時(shí)候處于休眠狀態(tài)。

2.4什么時(shí)候使用run loop

僅當(dāng)在為你的程序創(chuàng)建輔助線程的時(shí)候起趾,你才需要顯式運(yùn)行一個(gè)run loop诗舰。Run

loop是程序主線程基礎(chǔ)設(shè)施的關(guān)鍵部分。所以训裆,Cocoa和Carbon程序提供了代碼運(yùn)行主程序的循環(huán)并自動(dòng)啟動(dòng)run

loop眶根。IOS程序中UIApplication的run方法(或Mac OS

X中的NSApplication)作為程序啟動(dòng)步驟的一部分,它在程序正常啟動(dòng)的時(shí)候就會(huì)啟動(dòng)程序的主循環(huán)边琉。類(lèi)似

的属百,RunApplicationEventLoop函數(shù)為Carbon程序啟動(dòng)主循環(huán)。如果你使用xcode提供的模板創(chuàng)建你的程序变姨,那你永遠(yuǎn)不需要自

己去顯式的調(diào)用這些例程族扰。

對(duì)于輔助線程,你需要判斷一個(gè)run loop是否是必須的钳恕。如果是必須的别伏,那么你要自己配置并啟動(dòng)它。你不需要在任何情況下都去啟動(dòng)一個(gè)線程的run

loop忧额。比如,你使用線程來(lái)處理一個(gè)預(yù)先定義的長(zhǎng)時(shí)間運(yùn)行的任務(wù)時(shí)愧口,你應(yīng)該避免啟動(dòng)run loop睦番。Run

loop在你要和線程有更多的交互時(shí)才需要,比如以下情況:

使用端口或自定義輸入源來(lái)和其他線程通信

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

Cocoa中使用任何performSelector…的方法

使線程周期性工作

如果你決定在程序中使用run loop耍属,那么它的配置和啟動(dòng)都很簡(jiǎn)單托嚣。和所有線程編程一樣,你需要計(jì)劃好在輔助線程退出線程的情形厚骗。讓線程自然退出往往比強(qiáng)制關(guān)閉它更好示启。

-------- by wangzz

參考文檔:

http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i

Runtime

RunTime簡(jiǎn)稱(chēng)運(yùn)行時(shí)。就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制领舰,其中最主要的是消息機(jī)制夫嗓。對(duì)于C語(yǔ)言迟螺,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)( C語(yǔ)言的函數(shù)調(diào)用請(qǐng)看這里

)。編譯完成之后直接順序執(zhí)行舍咖,無(wú)任何二義性矩父。OC的函數(shù)調(diào)用成為消息發(fā)送。屬于動(dòng)態(tài)調(diào)用過(guò)程排霉。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)(事實(shí)證明窍株,在編

譯階段,OC可以調(diào)用任何函數(shù)攻柠,即使這個(gè)函數(shù)并未實(shí)現(xiàn)球订,只要申明過(guò)就不會(huì)報(bào)錯(cuò)。而C語(yǔ)言在編譯階段就會(huì)報(bào)錯(cuò))瑰钮。只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱(chēng)找

到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用辙售。

那OC是怎么實(shí)現(xiàn)動(dòng)態(tài)調(diào)用的呢?下面我們來(lái)看看OC通過(guò)發(fā)送消息來(lái)達(dá)到動(dòng)態(tài)調(diào)用的秘密飞涂。假如在OC中寫(xiě)了這樣的一個(gè)代碼:

[obj makeText];

其中obj是一個(gè)對(duì)象旦部,makeText是一個(gè)函數(shù)名稱(chēng)。對(duì)于這樣一個(gè)簡(jiǎn)單的調(diào)用较店。在編譯時(shí)RunTime會(huì)將上述代碼轉(zhuǎn)化成

objc_msgSend(obj,@selector(makeText));

首先我們來(lái)看看obj這個(gè)對(duì)象士八,iOS中的obj都繼承于NSObject。

@interface NSObject {

Class isa OBJC_ISA_AVAILABILITY;

}

在NSObjcet中存在一個(gè)Class的isa指針梁呈。然后我們看看Class:

typedef struct objc_class *Class;

struct objc_class {

Class isa;// 指向metaclass

Class super_class ;// 指向其父類(lèi)

const char *name ;// 類(lèi)名

long version ;// 類(lèi)的版本信息婚度,初始化默認(rèn)為0,可以通過(guò)runtime函數(shù)class_setVersion和class_getVersion進(jìn)行修改官卡、讀取

long info;// 一些標(biāo)識(shí)信息,如CLS_CLASS (0x1L) 表示該類(lèi)為普通 class 蝗茁,其中包含對(duì)象方法和成員變量;CLS_META (0x2L) 表示該類(lèi)為 metaclass,其中包含類(lèi)方法;

long instance_size ;// 該類(lèi)的實(shí)例變量大小(包括從父類(lèi)繼承下來(lái)的實(shí)例變量);

struct objc_ivar_list *ivars;// 用于存儲(chǔ)每個(gè)成員變量的地址

struct objc_method_list **methodLists ;// 與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲(chǔ)對(duì)象方法寻咒,如CLS_META (0x2L)哮翘,則存儲(chǔ)類(lèi)方法;

struct objc_cache *cache;// 指向最近使用的方法的指針,用于提升效率毛秘;

struct objc_protocol_list *protocols;// 存儲(chǔ)該類(lèi)遵守的協(xié)議

}

我們可以看到饭寺,對(duì)于一個(gè)Class類(lèi)中,存在很多東西叫挟,下面我來(lái)一一解釋一下:

Class

isa:指向metaclass艰匙,也就是靜態(tài)的Class。一般一個(gè)Obj對(duì)象中的isa會(huì)指向普通的Class抹恳,這個(gè)Class中存儲(chǔ)普通成員變量和對(duì)

象方法(“-”開(kāi)頭的方法)员凝,普通Class中的isa指針指向靜態(tài)Class,靜態(tài)Class中存儲(chǔ)static類(lèi)型成員變量和類(lèi)方法(“+”開(kāi)頭的方

法)奋献。

Class super_class:指向父類(lèi)健霹,如果這個(gè)類(lèi)是根類(lèi)旺上,則為NULL。

下面一張圖片很好的描述了類(lèi)和對(duì)象的繼承關(guān)系:

image

注意:所有metaclass中isa指針都指向跟metaclass骤公。而跟metaclass則指向自身抚官。Root metaclass是通過(guò)繼承Root class產(chǎn)生的。與root class結(jié)構(gòu)體成員一致阶捆,也就是前面提到的結(jié)構(gòu)凌节。不同的是Root metaclass的isa指針指向自身。

Class類(lèi)中其他的成員這里就先不做過(guò)多解釋了洒试,下面我們來(lái)看看:

@selector (makeText):這是一個(gè)SEL方法選擇器倍奢。SEL其主要作用是快速的通過(guò)方法名字(makeText)查找到對(duì)應(yīng)方法的函數(shù)指針,然后調(diào)用其函數(shù)垒棋。SEL其本身是一個(gè)Int類(lèi)型的一個(gè)地址卒煞,地址中存放著方法的名字。對(duì)于一個(gè)類(lèi)中叼架。每一個(gè)方法對(duì)應(yīng)著一個(gè)SEL畔裕。所以iOS類(lèi)中不能存在2個(gè)名稱(chēng)相同的方法,即使參數(shù)類(lèi)型不同乖订,因?yàn)镾EL是根據(jù)方法名字生成的扮饶,相同的方法名稱(chēng)只能對(duì)應(yīng)一個(gè)SEL。

下面我們就來(lái)看看具體消息發(fā)送之后是怎么來(lái)動(dòng)態(tài)查找對(duì)應(yīng)的方法的乍构。

首先甜无,編譯器將代碼[obj makeText];轉(zhuǎn)化為objc_msgSend(obj, @selector

(makeText));,在objc_msgSend函數(shù)中哥遮。首先通過(guò)obj的isa指針找到obj對(duì)應(yīng)的class岂丘。在Class中先去cache中通過(guò)SEL查找對(duì)應(yīng)函數(shù)method(猜測(cè)cache中method列表是以SEL為key通過(guò)hash表來(lái)存儲(chǔ)的,這樣能提高函數(shù)查找速度)眠饮,若cache中未找到奥帘。再去methodList中查找,若methodlist中未找到君仆,則取superClass中查找翩概。若能找到,則將method加入到cache中返咱,以方便下次查找,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行牍鞠。

Runtime應(yīng)用

1.什么是runtime?

runtime是一套底層的C語(yǔ)言API咖摹,包含很多強(qiáng)大實(shí)用的C語(yǔ)言數(shù)據(jù)類(lèi)型和C語(yǔ)言函數(shù),平時(shí)我們編寫(xiě)的OC代碼难述,底層都是基于runtime實(shí)現(xiàn)的萤晴。

2.runtime有什么作用吐句?

1.能動(dòng)態(tài)產(chǎn)生一個(gè)類(lèi),一個(gè)成員變量店读,一個(gè)方法

2.能動(dòng)態(tài)修改一個(gè)類(lèi)嗦枢,一個(gè)成員變量,一個(gè)方法

3.能動(dòng)態(tài)刪除一個(gè)類(lèi)屯断,一個(gè)成員變量文虏,一個(gè)方法

3.常用的頭文件

import包含對(duì)類(lèi)、成員變量殖演、屬性氧秘、方法的操作#import包含消息機(jī)制

4.常用方法

class_copyIvarList()返回一個(gè)指向類(lèi)的成員變量數(shù)組的指針

class_copyPropertyList()返回一個(gè)指向類(lèi)的屬性數(shù)組的指針

注意:根據(jù)Apple官方runtime.h文檔所示,上面兩個(gè)方法返回的指針趴久,在使用完畢之后必須free()丸相。

ivar_getName()獲取成員變量名-->C類(lèi)型的字符串property_getName()獲取屬性名-->C類(lèi)型的字符串-------------------------------------typedef struct objc_method *Method;class_getInstanceMethod() class_getClassMethod()以上兩個(gè)函數(shù)傳入返回Method類(lèi)型---------------------------------------------------method_exchangeImplementations()交換兩個(gè)方法的實(shí)現(xiàn)

5.runtime在開(kāi)發(fā)中的用途

1.動(dòng)態(tài)的遍歷一個(gè)類(lèi)的所有成員變量,用于字典轉(zhuǎn)模型,歸檔解檔操作

代碼如下:

  • (void)viewDidLoad { [superviewDidLoad];/** 利用runtime遍歷一個(gè)類(lèi)的全部成員變量

1.導(dǎo)入頭文件 /unsignedintcount =0;/* Ivar:表示成員變量類(lèi)型 /Ivar ivars = class_copyIvarList([BDPerson class], &count);//獲得一個(gè)指向該類(lèi)成員變量的指針for(inti =0; i < count; i ++) {//獲得IvarIvar ivar = ivars[i];//根據(jù)ivar獲得其成員變量的名稱(chēng)--->C語(yǔ)言的字符串constcharname = ivar_getName(ivar);NSStringkey = [NSStringstringWithUTF8String:name];NSLog(@"%d----%@",i,key);}}

運(yùn)行結(jié)果如下:

image

成員變量遍歷輸出結(jié)果.png

獲取一個(gè)類(lèi)的全部屬性:

image

獲取類(lèi)的屬性的代碼實(shí)現(xiàn).png

結(jié)果如下:

image

輸出結(jié)果.png

應(yīng)用場(chǎng)景:

可以利用遍歷類(lèi)的屬性彼棍,來(lái)快速的進(jìn)行歸檔操作灭忠。

將從網(wǎng)絡(luò)上下載的json數(shù)據(jù)進(jìn)行字典轉(zhuǎn)模型。

注意:歸檔解檔需要遵守協(xié)議座硕,實(shí)現(xiàn)以下兩個(gè)方法

  • (void)encodeWithCoder:(NSCoder*)encoder{

//歸檔存儲(chǔ)自定義對(duì)象

unsignedintcount =0;

//獲得指向該類(lèi)所有屬性的指針

objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);

for(inti =0; i < count; i ++) {

//獲得

objc_property_t property = properties[I];

//根據(jù)objc_property_t獲得其屬性的名稱(chēng)--->C語(yǔ)言的字符串

constchar*name = property_getName(property);

NSString*key = [NSStringstringWithUTF8String:name];

// 編碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值

[encoder encodeObject:[selfvalueForKeyPath:key] forKey:key];

}

}

  • (instancetype)initWithCoder:(NSCoder*)decoder{

//歸檔存儲(chǔ)自定義對(duì)象

unsignedintcount =0;

//獲得指向該類(lèi)所有屬性的指針

objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);

for(inti =0; i < count; i ++) {

objc_property_t property = properties[I];

//根據(jù)objc_property_t獲得其屬性的名稱(chēng)--->C語(yǔ)言的字符串

constchar*name = property_getName(property);

NSString*key = [NSStringstringWithUTF8String:name];

//解碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值

[selfsetValue:[decoder decodeObjectForKey:key] forKeyPath:key];

}

returnself;

}

二弛作、交換方法

通過(guò)runtime的method_exchangeImplementations(Method m1, Method m2)方法,可以進(jìn)行交換方法的實(shí)現(xiàn)坎吻;一般用自己寫(xiě)的方法(常用在自己寫(xiě)的框架中缆蝉,添加某些防錯(cuò)措施)來(lái)替換系統(tǒng)的方法實(shí)現(xiàn),常用的地方有:

在數(shù)組中瘦真,越界訪問(wèn)程序會(huì)崩刊头,可以用自己的方法添加判斷防止程序出現(xiàn)崩潰數(shù)組或字典中不能添加nil,如果添加程序會(huì)崩诸尽,用自己的方法替換系統(tǒng)防止系統(tǒng)崩潰原杂。

代碼實(shí)現(xiàn)如下:

image

運(yùn)行程序崩潰.png

image

添加一個(gè)分類(lèi)實(shí)現(xiàn)方法交換.png

再次運(yùn)行剛才的程序:

image

除了獲取屬性列表之外,還有方法調(diào)用您机,攔截調(diào)用穿肄,動(dòng)態(tài)添加方法屬性,關(guān)聯(lián)對(duì)象(添加屬性)际看,方法交換咸产,根據(jù)屬性值獲取屬性名稱(chēng)(反射機(jī)制)等應(yīng)用。

總結(jié)

runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的仲闽,也是非常重要的脑溢, 在面試過(guò)程中是經(jīng)常會(huì)被問(wèn)到的, 所以大家有必要進(jìn)行研究赖欣。

參考博客:

http://blog.csdn.net/ztp800201/article/details/9240913

http://www.reibang.com/p/613916eea37f

http://www.reibang.com/p/ebc6e20b84cf

http://www.cocoachina.com/ios/20141018/9960.html

http://www.reibang.com/p/364eab29f4f5

http://www.reibang.com/p/927c8384855a

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屑彻,一起剝皮案震驚了整個(gè)濱河市验庙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌社牲,老刑警劉巖粪薛,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搏恤,居然都是意外死亡违寿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)挑社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陨界,“玉大人,你說(shuō)我怎么就攤上這事痛阻【瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵阱当,是天一觀的道長(zhǎng)俏扩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)弊添,這世上最難降的妖魔是什么录淡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮油坝,結(jié)果婚禮上嫉戚,老公的妹妹穿的比我還像新娘。我一直安慰自己澈圈,他們只是感情好彬檀,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瞬女,像睡著了一般窍帝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诽偷,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天坤学,我揣著相機(jī)與錄音,去河邊找鬼报慕。 笑死深浮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的眠冈。 我是一名探鬼主播略号,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洋闽!你這毒婦竟也來(lái)了玄柠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诫舅,失蹤者是張志新(化名)和其女友劉穎羽利,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刊懈,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡这弧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虚汛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匾浪。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奏瞬,靈堂內(nèi)的尸體忽然破棺而出皇筛,到底是詐尸還是另有隱情漫谷,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布冷溶,位于F島的核電站,受9級(jí)特大地震影響尊浓,放射性物質(zhì)發(fā)生泄漏逞频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一栋齿、第九天 我趴在偏房一處隱蔽的房頂上張望苗胀。 院中可真熱鬧,春花似錦瓦堵、人聲如沸基协。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)堡掏。三九已至,卻和暖如春刨疼,著一層夾襖步出監(jiān)牢的瞬間泉唁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工揩慕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亭畜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓迎卤,卻偏偏與公主長(zhǎng)得像拴鸵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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

  • 這是一篇對(duì)Run Loop開(kāi)發(fā)文檔《Threading Program Guide:Run Loops》的翻譯劲藐,來(lái)...
    鴻雁長(zhǎng)飛光不度閱讀 3,649評(píng)論 3 29
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的八堡,也是非常重要的, 在面試過(guò)程中是經(jīng)常會(huì)被問(wèn)到的聘芜, ...
    SOI閱讀 21,819評(píng)論 3 63
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的兄渺,也是非常重要的, 在面試過(guò)程中是經(jīng)常會(huì)被問(wèn)到的汰现, ...
    made_China閱讀 1,210評(píng)論 0 7
  • 什么是Run Loops RunLoops是與線程相關(guān)聯(lián)的基礎(chǔ)部分挂谍,一個(gè)Run Loop就是事件處理循環(huán),他是用來(lái)...
    傻傻小蘿卜閱讀 971評(píng)論 0 5
  • ======================= 前言 RunLoop 是 iOS 和 OSX 開(kāi)發(fā)中非诚顾牵基礎(chǔ)的一個(gè)...
    i憬銘閱讀 883評(píng)論 0 4