【轉(zhuǎn)載】Objective-C語言的動(dòng)態(tài)性總結(jié)(編譯時(shí)與運(yùn)行時(shí))

編譯時(shí)與運(yùn)行時(shí)

編譯時(shí): 即編譯器對(duì)語言的編譯階段,編譯時(shí)只是對(duì)語言進(jìn)行最基本的檢查報(bào)錯(cuò),包括詞法分析、語法分析等等,將程序代碼翻譯成計(jì)算機(jī)能夠識(shí)別的語言(例如匯編等),編譯通過并不意味著程序就可以成功運(yùn)行。

運(yùn)行時(shí): 即程序通過了編譯這一關(guān)之后編譯好的代碼被裝載到內(nèi)存中跑起來的階段,這個(gè)時(shí)候會(huì)具體對(duì)類型進(jìn)行檢查,而不僅僅是對(duì)代碼的簡(jiǎn)單掃描分析,此時(shí)若出錯(cuò)程序會(huì)崩潰。

可以說編譯時(shí)是一個(gè)靜態(tài)的階段,類型錯(cuò)誤很明顯可以直接檢查出來,可讀性也好;而運(yùn)行時(shí)則是動(dòng)態(tài)的階段,開始具體與運(yùn)行環(huán)境結(jié)合起來。

OC語言的動(dòng)態(tài)性

OC語言的動(dòng)態(tài)性主要體現(xiàn)在三個(gè)方面:動(dòng)態(tài)類型(Dynamic typing)、動(dòng)態(tài)綁定(Dynamic binding)和動(dòng)態(tài)加載(Dynamic loading)。

動(dòng)態(tài)類型

動(dòng)態(tài)類型指的是對(duì)象指針類型的動(dòng)態(tài)性,具體是指使用id任意類型將對(duì)象的類型確定推遲到運(yùn)行時(shí),由賦給它的對(duì)象類型決定對(duì)象指針的類型。另外類型確定推遲到運(yùn)行時(shí)之后,可以通過NSObject的isKindOfClass方法動(dòng)態(tài)判斷對(duì)象最后的類型(動(dòng)態(tài)類型識(shí)別)。也就是說id修飾的對(duì)象為動(dòng)態(tài)類型對(duì)象,其他在編譯器指明類型的為靜態(tài)類型對(duì)象,通常如果不需要涉及到多態(tài)的話還是要盡量使用靜態(tài)類型(原因上面已經(jīng)說到:錯(cuò)誤可以在編譯器提前查出,可讀性好)。

示例:

對(duì)于語句NSString* testObject = [[NSData alloc] init]; testObject在編譯時(shí)和運(yùn)行時(shí)分別是什么類型的對(duì)象?

首先testObject是一個(gè)指向某個(gè)對(duì)象的指針,不論何時(shí)指針的空間大小是固定的。

編譯時(shí): 指針的類型為NSString,即編譯時(shí)會(huì)被當(dāng)成一個(gè)NSString實(shí)例來處理,編譯器在類型檢查的時(shí)候如果發(fā)現(xiàn)類型不匹配則會(huì)給出黃色警告,該語句給指針賦值用的是一個(gè)NSData對(duì)象,則編譯時(shí)編譯器則會(huì)給出類型不匹配警告。但編譯時(shí)如果testObject調(diào)用NSString的方法編譯器會(huì)認(rèn)為是正確的,既不會(huì)警告也不會(huì)報(bào)錯(cuò)。

運(yùn)行時(shí): 運(yùn)行時(shí)指針指向的實(shí)際是一個(gè)NSData對(duì)象,因此如果指針調(diào)用了NSString的方法,雖然編譯時(shí)通過了,但運(yùn)行時(shí)會(huì)崩潰,因?yàn)镹SData對(duì)象沒有該方法;另外,雖然運(yùn)行時(shí)指針實(shí)際指向的是NSData,但編譯時(shí)編譯器并不知道(前面說了編譯器會(huì)把指針當(dāng)成NSString對(duì)象處理),因此如果試圖用這個(gè)指針調(diào)用NSData的方法會(huì)直接編譯不通過,給出紅色報(bào)錯(cuò),程序也運(yùn)行不起來。

下面給出測(cè)試?yán)樱?/p>

? // 1.編譯時(shí)編譯器認(rèn)為testObject是一個(gè)NSString對(duì)象,這里賦給它一個(gè)NSData對(duì)象編譯器給出黃色類型錯(cuò)誤警告,但運(yùn)行時(shí)卻是指向一個(gè)NSData對(duì)象

? ? NSString* testObject = [[NSData alloc] init];

? ? // 2.編譯器認(rèn)為testObject是NSString對(duì)象,所以允許其調(diào)用NSString的方法,這里編譯通過無警告和錯(cuò)誤

? ? [testObject stringByAppendingString:@"string"];

? ? // 3.但不允許其調(diào)用NSData的方法,下面這里編譯不通過給出紅色報(bào)錯(cuò)

? ? [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];

將上面第三句編譯不通過的注釋掉,然后在第二句打斷點(diǎn),編譯后讓程序跑起來到斷點(diǎn)出會(huì)看到testObject指針的類型是_NSZeroData,指向一個(gè)NSData對(duì)象。繼續(xù)運(yùn)行程序會(huì)崩潰,因?yàn)镹SData對(duì)象沒有NSString的stringByAppendingString這個(gè)方法。


那么,假設(shè)testObject是id類型會(huì)怎樣呢?

? ? // 1.id任意類型,編譯器就不會(huì)把testObject在當(dāng)成NSString對(duì)象了

? ? id testObject = [[NSData alloc] init];

? ? // 2.調(diào)用NSData的方法編譯通過

? ? [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];

? ? // 3.調(diào)用NSString的方法編譯也通過

? ? [testObject stringByAppendingString:@"string"];


結(jié)果是編譯完全通過,編譯時(shí)編譯器把testObject指針當(dāng)成任意類型,運(yùn)行時(shí)才確定testObject為NSData對(duì)象(斷點(diǎn)看指針的類型和上面的例子中結(jié)果一樣還是_NSZeroData,指向一個(gè)NSData對(duì)象),因此執(zhí)行NSData的函數(shù)正常,但執(zhí)行NSString的方法時(shí)還是崩潰了。通過這個(gè)例子也可以很清楚的知道id類型的作用了,將類型的確定延遲到了運(yùn)行時(shí),體現(xiàn)了OC語言的一種動(dòng)態(tài)性:動(dòng)態(tài)類型。

動(dòng)態(tài)類型識(shí)別方法(面向?qū)ο笳Z言的內(nèi)省Introspection特性)

1.首先是Class類型:

Class class = [NSObject class]; // 通過類名得到對(duì)應(yīng)的Class動(dòng)態(tài)類型

Class class = [obj class]; // 通過實(shí)例對(duì)象得到對(duì)應(yīng)的Class動(dòng)態(tài)類型

if([obj1 class] == [obj2 class]) // 判斷是不是相同類型的實(shí)例

2.Class動(dòng)態(tài)類型和類名字符串的相互轉(zhuǎn)換:

NSClassFromString(@”NSObject”); // 由類名字符串得到Class動(dòng)態(tài)類型

NSStringFromClass([NSObject class]); // 由類名的動(dòng)態(tài)類型得到類名字符串

NSStringFromClass([obj class]); // 由對(duì)象的動(dòng)態(tài)類型得到類名字符串

3.判斷對(duì)象是否屬于某種動(dòng)態(tài)類型:

-(BOOL)isKindOfClass:class // 判斷某個(gè)對(duì)象是否是動(dòng)態(tài)類型class的實(shí)例或其子類的實(shí)例

-(BOOL)isMemberOfClass:class // 與isKindOfClass不同的是,這里只判斷某個(gè)對(duì)象是否是class類型的實(shí)例,不放寬到其子類

4.判斷類中是否有對(duì)應(yīng)的方法:

-(BOOL)respondsTosSelector:(SEL)selector // 類中是否有這個(gè)類方法

-(BOOL)instancesRespondToSelector:(SEL)selector // 類中是否有這個(gè)實(shí)例方法

區(qū)別:

上面兩個(gè)方法都可以通過類名調(diào)用,前者判斷類中是否有對(duì)應(yīng)的類方法(通過‘+’修飾定義的方法),后者判斷類中是否有對(duì)應(yīng)的實(shí)例方法(通過‘-’修飾定義的方法)。此外,前者respondsTosSelector函數(shù)還可以被類的實(shí)例對(duì)象調(diào)用,效果等同于直接用類名調(diào)用后者instancesRespondToSelector函數(shù)。

舉個(gè)例子:假設(shè)有一個(gè)類Test,有它的一個(gè)實(shí)例對(duì)象test,Test類中定義了一個(gè)類函數(shù):+ (void)classFun;和一個(gè)實(shí)例函數(shù):- (void)objFunc;,那么各種調(diào)用情況的結(jié)果如下:

? ? [1][Test instancesRespondToSelector:@selector(objFunc)];//YES

? ? [2][Test instancesRespondToSelector:@selector(classFunc)];//NO

? ? [3][Test respondsToSelector:@selector(objFunc)];//NO

? ? [4][Test respondsToSelector:@selector(classFunc)];//YES

? ? [5][test respondsToSelector:@selector(objFunc)];//YES

? ? [6][test respondsToSelector:@selector(classFunc)];//NO


結(jié)論: 如果想判斷一個(gè)類中是否有某個(gè)類方法,應(yīng)該使用[4]; 如果想判斷一個(gè)類中是否有某個(gè)實(shí)例方法,可以使用[1]或者[5]。

5.方法名字符串和SEL類型的轉(zhuǎn)換

在編譯器,編譯器會(huì)根據(jù)方法的名字和參數(shù)序列生成唯一標(biāo)識(shí)改方法的ID,這個(gè)ID為SEL類型。到了運(yùn)行時(shí)編譯器通過SEL類型的ID來查找對(duì)應(yīng)的方法,方法的名字和參數(shù)序列相同,那么它們的ID就都是相同的。另外,可以通過@select()指示符獲得方法的ID。常用的方法如下:

SEL funcID = @select(func);// 這個(gè)注冊(cè)事件回調(diào)時(shí)常用,將方法轉(zhuǎn)成SEL類型

SEL funcID = NSSelectorFromString(@"func"); // 根據(jù)方法名得到方法標(biāo)識(shí)

NSString *funcName = NSStringFromSelector(funcID); // 根據(jù)SEL類型得到方法名字符串


動(dòng)態(tài)綁定

動(dòng)態(tài)綁定指的是方法確定的動(dòng)態(tài)性,具體指的是利用OC的消息傳遞機(jī)制將要執(zhí)行的方法的確定推遲到運(yùn)行時(shí),可以動(dòng)態(tài)添加方法。也就是說,一個(gè)OC對(duì)象是否調(diào)用某個(gè)方法不是由編譯器決定的,而是由運(yùn)行時(shí)決定的;另外關(guān)于動(dòng)態(tài)綁定的關(guān)鍵一點(diǎn)是基于消息傳遞機(jī)制的消息轉(zhuǎn)發(fā)機(jī)制,主要處理應(yīng)對(duì)一些接受者無法處理的消息,此時(shí)有機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其他接收者處理,具體見下面介紹。

動(dòng)態(tài)綁定是基于動(dòng)態(tài)類型的,在運(yùn)行時(shí)對(duì)象的類型確定后,那么對(duì)象的屬性和方法也就確定了(包括類中原來的屬性和方法以及運(yùn)行時(shí)動(dòng)態(tài)新加入的屬性和方法),這也就是所謂的動(dòng)態(tài)綁定了。動(dòng)態(tài)綁定的核心就該是在運(yùn)行時(shí)動(dòng)態(tài)的為類添加屬性和方法,以及方法的最后處理或轉(zhuǎn)發(fā),主要用到C語言語法,因?yàn)樯婕暗竭\(yùn)行時(shí),因此要引入運(yùn)行時(shí)頭文件:#include <objc/runtime.h>。

消息傳遞機(jī)制:

在OC中,方法的調(diào)用不再理解為對(duì)象調(diào)用其方法,而是要理解成對(duì)象接收消息,消息的發(fā)送采用‘動(dòng)態(tài)綁定’機(jī)制,具體會(huì)調(diào)用哪個(gè)方法直到運(yùn)行時(shí)才能確定,確定后才會(huì)去執(zhí)行綁定的代碼。方法的調(diào)用實(shí)際就是告訴對(duì)象要干什么,給對(duì)象(的指針)傳送一個(gè)消息,對(duì)象為接收者(receiver),調(diào)用的方法及其參數(shù)即消息(message),給一個(gè)對(duì)象傳消息表達(dá)為:[receiver message]; 接受者的類型可以通過動(dòng)態(tài)類型識(shí)別于運(yùn)行時(shí)確定。

在消息傳遞機(jī)制中,當(dāng)開發(fā)者編寫[receiver message];語句發(fā)送消息后,編譯器都會(huì)將其轉(zhuǎn)換成對(duì)應(yīng)的一條objc_msgSend C語言消息發(fā)送原語,具體格式為:

void objc_msgSend (id self, SEL cmd, ...)

這個(gè)原語函數(shù)參數(shù)可變,第一個(gè)參數(shù)填入消息的接受者,第二個(gè)參數(shù)是消息‘選擇子’,后面跟著可選的消息的參數(shù)。有了這些參數(shù),objc_msgSend就可以通過接受者的的isa指針,到其類對(duì)象中的方法列表中以選擇子的名稱為‘鍵’尋找對(duì)應(yīng)的方法,找到則轉(zhuǎn)到其實(shí)現(xiàn)代碼執(zhí)行,找不到則繼續(xù)根據(jù)繼承關(guān)系從父類中尋找,如果到了根類還是無法找到對(duì)應(yīng)的方法,說明該接受者對(duì)象無法響應(yīng)該消息,則會(huì)觸發(fā)‘消息轉(zhuǎn)發(fā)機(jī)制’,給開發(fā)者最后一次挽救程序崩潰的機(jī)會(huì)。

消息轉(zhuǎn)發(fā)機(jī)制:

【iOS沉思錄】OC消息傳遞機(jī)制三道防線:消息轉(zhuǎn)發(fā)機(jī)制詳解

動(dòng)態(tài)加載

動(dòng)態(tài)加載主要包括兩個(gè)方面,一個(gè)是動(dòng)態(tài)資源加載,一個(gè)是一些可執(zhí)行代碼模塊的加載,這些資源在運(yùn)行時(shí)根據(jù)需要?jiǎng)討B(tài)的選擇性的加入到程序中,是一種代碼和資源的‘懶加載’模式,可以降低內(nèi)存需求,提高整個(gè)程序的性能,另外也大大提高了可擴(kuò)展性。

例如:資源動(dòng)態(tài)加載中的圖片資源的屏幕適配,同一個(gè)圖片對(duì)象可能需要準(zhǔn)備幾種不同分辨率的圖片資源,程序會(huì)根據(jù)當(dāng)前的機(jī)型動(dòng)態(tài)選擇加載對(duì)應(yīng)分辨率的圖片,像iphone4之前老機(jī)型使用的是@1x的原始圖片,而retina顯示屏出現(xiàn)之后每個(gè)像素點(diǎn)被分成了四個(gè)像素,因此同樣尺寸的屏幕需要4倍分辨率(寬高各兩倍)的@2x圖片,最新的針對(duì)iphone6/6+以上的機(jī)型則需要@3x分辨率的圖片。例如下面所示應(yīng)用的AppIcon,需要根據(jù)機(jī)型以及機(jī)型分辨率動(dòng)態(tài)的選擇加載某張具體的圖片資源:

官方運(yùn)行時(shí)源碼:https://opensource.apple.com/source/objc4/objc4-532/runtime/

OC運(yùn)行時(shí)編程指南:Objective-C Runtime Programming Guide

Cocoa消息 Cocoa類與對(duì)象



問題:我們所說的Objective-C是動(dòng)態(tài)運(yùn)行時(shí)語言是什么意思?

主要指的是OC語言的動(dòng)態(tài)性,包括動(dòng)態(tài)性和多態(tài)性兩個(gè)方面。

動(dòng)態(tài)性:即OC的動(dòng)態(tài)類型、動(dòng)態(tài)綁定和動(dòng)態(tài)加載特性,將對(duì)象類型的確定、方法調(diào)用的確定、代碼和資源的裝載等推遲到運(yùn)行時(shí)進(jìn)行,更加靈活;

多態(tài):多態(tài)是面向?qū)ο笞兂烧Z言的特性,OC作為一門面向?qū)ο蟮恼Z言,自然具備這種多態(tài)性,多態(tài)性指的是來自不同類的對(duì)象可以接受同一消息的能力,或者說不同對(duì)象以自己的方式響應(yīng)相同的消息的能力。

問題:動(dòng)態(tài)綁定是在運(yùn)行時(shí)確定要調(diào)用的方法?

動(dòng)態(tài)綁定將調(diào)用方法的確定推遲到運(yùn)行時(shí)。在編譯時(shí),方法的調(diào)用并不和代碼綁定在一起,只有在消實(shí)發(fā)送出來之后,才確定被調(diào)用的代碼。通過動(dòng)態(tài)類型和動(dòng)態(tài)綁定技術(shù),代碼每次執(zhí)行都可以得到不同的結(jié)果。運(yùn)行時(shí)負(fù)責(zé)確定消息的接收者和被調(diào)用的方法。運(yùn)行時(shí)的消息分發(fā)機(jī)制為動(dòng)態(tài)綁定提供支持。當(dāng)向一個(gè)動(dòng)態(tài)類型確定了的對(duì)象發(fā)送消息時(shí),運(yùn)行環(huán)境會(huì)通過接收者的isa指針定位對(duì)象的類,并以此確定被調(diào)用的方法,方法是動(dòng)態(tài)綁定的。

問題:解釋OC中的id類型?id、nil代表什么?

id表示變量或?qū)ο蟮念愋驮诰帉懘a時(shí)(編譯期)不確定,視為任意類型,直到程序跑起來推遲到運(yùn)行時(shí)才最終確定其類型。id類似于C/C++中的void *,但id和void*并非完全一樣。id是一個(gè)指向繼承了NSObject的OC對(duì)象的指針,注意id是一個(gè)指針,雖然省略了*號(hào)。id和C語言的void*之間需要通過bridge關(guān)鍵字來顯式的橋接轉(zhuǎn)換。具體轉(zhuǎn)換方式示例如下:

id nsobj = [[NSObject alloc] init];

void *p = (__bridge void *)nsobj;

id nsobj = (__bridge id)p;

OC中的nil定義在objc/objc.h中,表示的是一個(gè)指向空的Objctive-C對(duì)象的指針。例如weak修飾的弱引用對(duì)象在指向的對(duì)象釋放時(shí)會(huì)自動(dòng)將指針置為nil,即空對(duì)象指針,防止‘指針懸掛’。

問題:instancetype和id的區(qū)別?

instancetype和id都可以用來代表任意類型,將對(duì)象的類型確定往后推遲,用于體現(xiàn)OC語言的動(dòng)態(tài)性,使其聲明的對(duì)象具有運(yùn)行時(shí)的特性。

它們的區(qū)別是:instancetype只能作為返回值類型,但在編譯期instancetype會(huì)進(jìn)行類型檢測(cè),因此對(duì)于所有返回類的實(shí)例的類方法或?qū)嵗椒?,建議返回值類型全部使用instancetype而不是id,具體原因后面舉例介紹;id類型既可以作為返回值類型,也可以作為參數(shù)類型,也可以作為變量的類型,但id類型在編譯期不會(huì)進(jìn)行類型檢測(cè)。

問題: 一般的方法method和OC中的選擇器selector有何不同?

selector是一個(gè)方法的名字,基于動(dòng)態(tài)綁定環(huán)境下,method是一個(gè)組合體,包含了名字和實(shí)現(xiàn)。

可以理解@selector()就是取類方法的編號(hào),他的行為基本可以等同C語言的中函數(shù)指針,只不過C語言中,可以把函數(shù)名直接賦給一個(gè)函數(shù)指針,而Objective-C的類不能直接應(yīng)用函數(shù)指針,這樣只能做一個(gè)@selector語法來取. 它的結(jié)果是一個(gè)SEL類型。這個(gè)類型本質(zhì)是類方法的編號(hào)(函數(shù)地址)。

問題:什么是目標(biāo)-動(dòng)作(target-action)機(jī)制?

目標(biāo)是動(dòng)作消息的接收者。例如一個(gè)控件,或者更為常見的是它的單元,

以插座變量的形式保有其動(dòng)作消息的目標(biāo)。 動(dòng)作是控件發(fā)送給目標(biāo)的消息,或者從目標(biāo)的角度看,它是目標(biāo)為了響應(yīng)動(dòng)作而實(shí)現(xiàn)的方法。程序需要某些機(jī)制來進(jìn)行事件和指令的翻譯。這個(gè)機(jī)制就是目標(biāo)-動(dòng)作機(jī)制。

問題:下列代碼取決于Objective-C的哪樣特性?

id myobj;

... ...

[myobj draw];

預(yù)處理機(jī)制

枚舉數(shù)據(jù)類型

靜態(tài)類型

動(dòng)態(tài)類型(right)

問題: Object-C有私有方法嗎? 私有變量呢?

首先要看私有的含義。私有主要指的是通過類的封裝性,將不希望讓外界看到的方法或?qū)傩噪[藏在類內(nèi)部,只有該類可以在內(nèi)部訪問,外部不可見不可訪問。

表面上OC中是可以實(shí)現(xiàn)私有的變量和方法的,即將它們隱藏不暴露在頭文件,不可以顯式地直接訪問,但是OC中這種私有并不是絕對(duì)的私有,例如即使將變量和方法隱藏在.m實(shí)現(xiàn)文件中,開發(fā)者仍然可以利用runtime運(yùn)行時(shí)機(jī)制強(qiáng)行訪問沒有暴露在頭文件的變量和方法。

OC中實(shí)現(xiàn)變量和方法‘私有‘的方式:

一種是在類的頭文件中生命私有變量:

#import <Foundation/Foundation.h>

@interface Test : NSObject {

? ? /* 頭文件中定義私有變量,默認(rèn)為@protected */

? ? @private

? ? NSString *major;

}

@end

另外一種是在.m實(shí)現(xiàn)文件頭部的類擴(kuò)展區(qū)域定義私有屬性或方法,其中方法可不用聲明,直接在實(shí)現(xiàn)文件中實(shí)現(xiàn)即可,只要不在頭文件生命的方法都對(duì)外不可見:

#import "Test.h"

@interface Test() {

? ? /* 類擴(kuò)展區(qū)域定義私有變量,默認(rèn)就是@private */

? ? int age;

}

/* 類擴(kuò)展區(qū)域定義私有屬性 */

@property (nonatomic, copy) NSString *name;

/* 類擴(kuò)展區(qū)域定義私有實(shí)例方法(可省略聲明,類方法的作用主要就是提供對(duì)外接口的,所以一般不會(huì)定義為私有) */

- (void)test;

@end

@implementation Test

/**

* 私有實(shí)例方法

*/

- (void)test {

? ? NSLog(@"這是個(gè)私有實(shí)例方法!");

}

@end



此文為轉(zhuǎn)發(fā)用于記錄總結(jié) ,感謝大神們的總結(jié)。

原文:https://blog.csdn.net/cordova/article/details/53876682

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容