編譯時與運行時
編譯時: 即編譯器對語言的編譯階段,編譯時只是對語言進行最基本的檢查報錯,包括詞法分析、語法分析等等,將程序代碼翻譯成計算機能夠識別的語言(例如匯編等),編譯通過并不意味著程序就可以成功運行。
運行時: 即程序通過了編譯這一關之后編譯好的代碼被裝載到內存中跑起來的階段,這個時候會具體對類型進行檢查,而不僅僅是對代碼的簡單掃描分析,此時若出錯程序會崩潰。
可以說編譯時是一個靜態(tài)的階段,類型錯誤很明顯可以直接檢查出來,可讀性也好;而運行時則是動態(tài)的階段,開始具體與運行環(huán)境結合起來。
OC語言的動態(tài)性
OC語言的動態(tài)性主要體現(xiàn)在三個方面:動態(tài)類型(Dynamic typing)、動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)。
動態(tài)類型
動態(tài)類型指的是對象指針類型的動態(tài)性,具體是指使用id任意類型將對象的類型確定推遲到運行時,由賦給它的對象類型決定對象指針的類型。另外類型確定推遲到運行時之后,可以通過NSObject的isKindOfClass方法動態(tài)判斷對象最后的類型(動態(tài)類型識別)。也就是說id修飾的對象為動態(tài)類型對象,其他在編譯器指明類型的為靜態(tài)類型對象,通常如果不需要涉及到多態(tài)的話還是要盡量使用靜態(tài)類型(原因上面已經說到:錯誤可以在編譯器提前查出,可讀性好)。
示例:
對于語句NSString* testObject = [[NSData alloc] init]; testObject在編譯時和運行時分別是什么類型的對象?
首先testObject是一個指向某個對象的指針,不論何時指針的空間大小是固定的。
編譯時: 指針的類型為NSString,即編譯時會被當成一個NSString實例來處理,編譯器在類型檢查的時候如果發(fā)現(xiàn)類型不匹配則會給出黃色警告,該語句給指針賦值用的是一個NSData對象,則編譯時編譯器則會給出類型不匹配警告。但編譯時如果testObject調用NSString的方法編譯器會認為是正確的,既不會警告也不會報錯。
運行時: 運行時指針指向的實際是一個NSData對象,因此如果指針調用了NSString的方法,雖然編譯時通過了,但運行時會崩潰,因為NSData對象沒有該方法;另外,雖然運行時指針實際指向的是NSData,但編譯時編譯器并不知道(前面說了編譯器會把指針當成NSString對象處理),因此如果試圖用這個指針調用NSData的方法會直接編譯不通過,給出紅色報錯,程序也運行不起來。
下面給出測試例子:
? // 1.編譯時編譯器認為testObject是一個NSString對象,這里賦給它一個NSData對象編譯器給出黃色類型錯誤警告,但運行時卻是指向一個NSData對象
? ? NSString* testObject = [[NSData alloc] init];
? ? // 2.編譯器認為testObject是NSString對象,所以允許其調用NSString的方法,這里編譯通過無警告和錯誤
? ? [testObject stringByAppendingString:@"string"];
? ? // 3.但不允許其調用NSData的方法,下面這里編譯不通過給出紅色報錯
? ? [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
將上面第三句編譯不通過的注釋掉,然后在第二句打斷點,編譯后讓程序跑起來到斷點出會看到testObject指針的類型是_NSZeroData,指向一個NSData對象。繼續(xù)運行程序會崩潰,因為NSData對象沒有NSString的stringByAppendingString這個方法。

那么,假設testObject是id類型會怎樣呢?
? ? // 1.id任意類型,編譯器就不會把testObject在當成NSString對象了
? ? id testObject = [[NSData alloc] init];
? ? // 2.調用NSData的方法編譯通過
? ? [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
? ? // 3.調用NSString的方法編譯也通過
? ? [testObject stringByAppendingString:@"string"];
結果是編譯完全通過,編譯時編譯器把testObject指針當成任意類型,運行時才確定testObject為NSData對象(斷點看指針的類型和上面的例子中結果一樣還是_NSZeroData,指向一個NSData對象),因此執(zhí)行NSData的函數(shù)正常,但執(zhí)行NSString的方法時還是崩潰了。通過這個例子也可以很清楚的知道id類型的作用了,將類型的確定延遲到了運行時,體現(xiàn)了OC語言的一種動態(tài)性:動態(tài)類型。
動態(tài)類型識別方法(面向對象語言的內省Introspection特性)
1.首先是Class類型:
Class class = [NSObject class]; // 通過類名得到對應的Class動態(tài)類型
Class class = [obj class]; // 通過實例對象得到對應的Class動態(tài)類型
if([obj1 class] == [obj2 class]) // 判斷是不是相同類型的實例
2.Class動態(tài)類型和類名字符串的相互轉換:
NSClassFromString(@”NSObject”); // 由類名字符串得到Class動態(tài)類型
NSStringFromClass([NSObject class]); // 由類名的動態(tài)類型得到類名字符串
NSStringFromClass([obj class]); // 由對象的動態(tài)類型得到類名字符串
3.判斷對象是否屬于某種動態(tài)類型:
-(BOOL)isKindOfClass:class // 判斷某個對象是否是動態(tài)類型class的實例或其子類的實例
-(BOOL)isMemberOfClass:class // 與isKindOfClass不同的是,這里只判斷某個對象是否是class類型的實例,不放寬到其子類
4.判斷類中是否有對應的方法:
-(BOOL)respondsTosSelector:(SEL)selector // 類中是否有這個類方法
-(BOOL)instancesRespondToSelector:(SEL)selector // 類中是否有這個實例方法
區(qū)別:
上面兩個方法都可以通過類名調用,前者判斷類中是否有對應的類方法(通過‘+’修飾定義的方法),后者判斷類中是否有對應的實例方法(通過‘-’修飾定義的方法)。此外,前者respondsTosSelector函數(shù)還可以被類的實例對象調用,效果等同于直接用類名調用后者instancesRespondToSelector函數(shù)。
舉個例子:假設有一個類Test,有它的一個實例對象test,Test類中定義了一個類函數(shù):+ (void)classFun;和一個實例函數(shù):- (void)objFunc;,那么各種調用情況的結果如下:
? ? [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
結論: 如果想判斷一個類中是否有某個類方法,應該使用[4]; 如果想判斷一個類中是否有某個實例方法,可以使用[1]或者[5]。
5.方法名字符串和SEL類型的轉換
在編譯器,編譯器會根據(jù)方法的名字和參數(shù)序列生成唯一標識改方法的ID,這個ID為SEL類型。到了運行時編譯器通過SEL類型的ID來查找對應的方法,方法的名字和參數(shù)序列相同,那么它們的ID就都是相同的。另外,可以通過@select()指示符獲得方法的ID。常用的方法如下:
SEL funcID = @select(func);// 這個注冊事件回調時常用,將方法轉成SEL類型
SEL funcID = NSSelectorFromString(@"func"); // 根據(jù)方法名得到方法標識
NSString *funcName = NSStringFromSelector(funcID); // 根據(jù)SEL類型得到方法名字符串
動態(tài)綁定
動態(tài)綁定指的是方法確定的動態(tài)性,具體指的是利用OC的消息傳遞機制將要執(zhí)行的方法的確定推遲到運行時,可以動態(tài)添加方法。也就是說,一個OC對象是否調用某個方法不是由編譯器決定的,而是由運行時決定的;另外關于動態(tài)綁定的關鍵一點是基于消息傳遞機制的消息轉發(fā)機制,主要處理應對一些接受者無法處理的消息,此時有機會將消息轉發(fā)給其他接收者處理,具體見下面介紹。
動態(tài)綁定是基于動態(tài)類型的,在運行時對象的類型確定后,那么對象的屬性和方法也就確定了(包括類中原來的屬性和方法以及運行時動態(tài)新加入的屬性和方法),這也就是所謂的動態(tài)綁定了。動態(tài)綁定的核心就該是在運行時動態(tài)的為類添加屬性和方法,以及方法的最后處理或轉發(fā),主要用到C語言語法,因為涉及到運行時,因此要引入運行時頭文件:#include <objc/runtime.h>。
消息傳遞機制:
在OC中,方法的調用不再理解為對象調用其方法,而是要理解成對象接收消息,消息的發(fā)送采用‘動態(tài)綁定’機制,具體會調用哪個方法直到運行時才能確定,確定后才會去執(zhí)行綁定的代碼。方法的調用實際就是告訴對象要干什么,給對象(的指針)傳送一個消息,對象為接收者(receiver),調用的方法及其參數(shù)即消息(message),給一個對象傳消息表達為:[receiver message]; 接受者的類型可以通過動態(tài)類型識別于運行時確定。
在消息傳遞機制中,當開發(fā)者編寫[receiver message];語句發(fā)送消息后,編譯器都會將其轉換成對應的一條objc_msgSend C語言消息發(fā)送原語,具體格式為:
void objc_msgSend (id self, SEL cmd, ...)
這個原語函數(shù)參數(shù)可變,第一個參數(shù)填入消息的接受者,第二個參數(shù)是消息‘選擇子’,后面跟著可選的消息的參數(shù)。有了這些參數(shù),objc_msgSend就可以通過接受者的的isa指針,到其類對象中的方法列表中以選擇子的名稱為‘鍵’尋找對應的方法,找到則轉到其實現(xiàn)代碼執(zhí)行,找不到則繼續(xù)根據(jù)繼承關系從父類中尋找,如果到了根類還是無法找到對應的方法,說明該接受者對象無法響應該消息,則會觸發(fā)‘消息轉發(fā)機制’,給開發(fā)者最后一次挽救程序崩潰的機會。
消息轉發(fā)機制:
【iOS沉思錄】OC消息傳遞機制三道防線:消息轉發(fā)機制詳解
動態(tài)加載
動態(tài)加載主要包括兩個方面,一個是動態(tài)資源加載,一個是一些可執(zhí)行代碼模塊的加載,這些資源在運行時根據(jù)需要動態(tài)的選擇性的加入到程序中,是一種代碼和資源的‘懶加載’模式,可以降低內存需求,提高整個程序的性能,另外也大大提高了可擴展性。
例如:資源動態(tài)加載中的圖片資源的屏幕適配,同一個圖片對象可能需要準備幾種不同分辨率的圖片資源,程序會根據(jù)當前的機型動態(tài)選擇加載對應分辨率的圖片,像iphone4之前老機型使用的是@1x的原始圖片,而retina顯示屏出現(xiàn)之后每個像素點被分成了四個像素,因此同樣尺寸的屏幕需要4倍分辨率(寬高各兩倍)的@2x圖片,最新的針對iphone6/6+以上的機型則需要@3x分辨率的圖片。例如下面所示應用的AppIcon,需要根據(jù)機型以及機型分辨率動態(tài)的選擇加載某張具體的圖片資源:
官方運行時源碼:https://opensource.apple.com/source/objc4/objc4-532/runtime/
OC運行時編程指南:Objective-C Runtime Programming Guide
問題:我們所說的Objective-C是動態(tài)運行時語言是什么意思?
主要指的是OC語言的動態(tài)性,包括動態(tài)性和多態(tài)性兩個方面。
動態(tài)性:即OC的動態(tài)類型、動態(tài)綁定和動態(tài)加載特性,將對象類型的確定、方法調用的確定、代碼和資源的裝載等推遲到運行時進行,更加靈活;
多態(tài):多態(tài)是面向對象變成語言的特性,OC作為一門面向對象的語言,自然具備這種多態(tài)性,多態(tài)性指的是來自不同類的對象可以接受同一消息的能力,或者說不同對象以自己的方式響應相同的消息的能力。
問題:動態(tài)綁定是在運行時確定要調用的方法?
動態(tài)綁定將調用方法的確定推遲到運行時。在編譯時,方法的調用并不和代碼綁定在一起,只有在消實發(fā)送出來之后,才確定被調用的代碼。通過動態(tài)類型和動態(tài)綁定技術,代碼每次執(zhí)行都可以得到不同的結果。運行時負責確定消息的接收者和被調用的方法。運行時的消息分發(fā)機制為動態(tài)綁定提供支持。當向一個動態(tài)類型確定了的對象發(fā)送消息時,運行環(huán)境會通過接收者的isa指針定位對象的類,并以此確定被調用的方法,方法是動態(tài)綁定的。
問題:解釋OC中的id類型?id、nil代表什么?
id表示變量或對象的類型在編寫代碼時(編譯期)不確定,視為任意類型,直到程序跑起來推遲到運行時才最終確定其類型。id類似于C/C++中的void *,但id和void*并非完全一樣。id是一個指向繼承了NSObject的OC對象的指針,注意id是一個指針,雖然省略了*號。id和C語言的void*之間需要通過bridge關鍵字來顯式的橋接轉換。具體轉換方式示例如下:
id nsobj = [[NSObject alloc] init];
void *p = (__bridge void *)nsobj;
id nsobj = (__bridge id)p;
OC中的nil定義在objc/objc.h中,表示的是一個指向空的Objctive-C對象的指針。例如weak修飾的弱引用對象在指向的對象釋放時會自動將指針置為nil,即空對象指針,防止‘指針懸掛’。
問題:instancetype和id的區(qū)別?
instancetype和id都可以用來代表任意類型,將對象的類型確定往后推遲,用于體現(xiàn)OC語言的動態(tài)性,使其聲明的對象具有運行時的特性。
它們的區(qū)別是:instancetype只能作為返回值類型,但在編譯期instancetype會進行類型檢測,因此對于所有返回類的實例的類方法或實例方法,建議返回值類型全部使用instancetype而不是id,具體原因后面舉例介紹;id類型既可以作為返回值類型,也可以作為參數(shù)類型,也可以作為變量的類型,但id類型在編譯期不會進行類型檢測。
問題: 一般的方法method和OC中的選擇器selector有何不同?
selector是一個方法的名字,基于動態(tài)綁定環(huán)境下,method是一個組合體,包含了名字和實現(xiàn)。
可以理解@selector()就是取類方法的編號,他的行為基本可以等同C語言的中函數(shù)指針,只不過C語言中,可以把函數(shù)名直接賦給一個函數(shù)指針,而Objective-C的類不能直接應用函數(shù)指針,這樣只能做一個@selector語法來取. 它的結果是一個SEL類型。這個類型本質是類方法的編號(函數(shù)地址)。
問題:什么是目標-動作(target-action)機制?
目標是動作消息的接收者。例如一個控件,或者更為常見的是它的單元,
以插座變量的形式保有其動作消息的目標。 動作是控件發(fā)送給目標的消息,或者從目標的角度看,它是目標為了響應動作而實現(xiàn)的方法。程序需要某些機制來進行事件和指令的翻譯。這個機制就是目標-動作機制。
問題:下列代碼取決于Objective-C的哪樣特性?
id myobj;
... ...
[myobj draw];
預處理機制
枚舉數(shù)據(jù)類型
靜態(tài)類型
動態(tài)類型(right)
問題: Object-C有私有方法嗎? 私有變量呢?
首先要看私有的含義。私有主要指的是通過類的封裝性,將不希望讓外界看到的方法或屬性隱藏在類內部,只有該類可以在內部訪問,外部不可見不可訪問。
表面上OC中是可以實現(xiàn)私有的變量和方法的,即將它們隱藏不暴露在頭文件,不可以顯式地直接訪問,但是OC中這種私有并不是絕對的私有,例如即使將變量和方法隱藏在.m實現(xiàn)文件中,開發(fā)者仍然可以利用runtime運行時機制強行訪問沒有暴露在頭文件的變量和方法。
OC中實現(xiàn)變量和方法‘私有‘的方式:
一種是在類的頭文件中生命私有變量:
#import <Foundation/Foundation.h>
@interface Test : NSObject {
? ? /* 頭文件中定義私有變量,默認為@protected */
? ? @private
? ? NSString *major;
}
@end
另外一種是在.m實現(xiàn)文件頭部的類擴展區(qū)域定義私有屬性或方法,其中方法可不用聲明,直接在實現(xiàn)文件中實現(xiàn)即可,只要不在頭文件生命的方法都對外不可見:
#import "Test.h"
@interface Test() {
? ? /* 類擴展區(qū)域定義私有變量,默認就是@private */
? ? int age;
}
/* 類擴展區(qū)域定義私有屬性 */
@property (nonatomic, copy) NSString *name;
/* 類擴展區(qū)域定義私有實例方法(可省略聲明,類方法的作用主要就是提供對外接口的,所以一般不會定義為私有) */
- (void)test;
@end
@implementation Test
/**
* 私有實例方法
*/
- (void)test {
? ? NSLog(@"這是個私有實例方法!");
}
@end
此文為轉發(fā)用于記錄總結 ,感謝大神們的總結。