1. 程式人生 > >[執行時]Objective-C的執行時程式設計(Runtime Programming)

[執行時]Objective-C的執行時程式設計(Runtime Programming)

以前還真沒了解過Objective-C的執行時程式設計(Runtime Programming),今天特意在網上搜了下,原來這麼深奧啊 表示現在理解不了,先轉走了再說,之前轉載的文章都是大神們總結的綜合,轉載地址忘記註明了 ,抱歉。

--  [1] 版本和平臺

  --  [2] 與Runtime System互動

  --  [3] 方法的動態決議

  --  [4] 訊息轉發

  --  [5] 型別編碼

  --  [6] 屬性宣告

[1] 版本和平臺

Runtime System對於Objective-C來說就好比是它的作業系統,或者說是執行的支撐平臺,它使得Objective-C程式碼能夠按照既定的語言特性跑起來。

相對於C/C++來說,Objective-C儘可能地把一些動作推遲到執行時來執行,即儘可能動態地做事情。因此,它不僅需要一個編譯器,還需要一個執行時環境來執行編譯後的程式碼。

Runtime System分為Legacy和Modern兩個版本,一般來說,我們現在用的都是Modern版本。Modern版本的Runtime System有一個顯著的特徵就是“non-fragile”,即父類的成員變數的佈局發生改變時,子類不需要重新編譯。此外,還支援為宣告的屬性進行合成操作(即@property@synthesis)。

下面會討論NSObject類Objective-C程式如何與Runtime System互動

執行時動態地載入類發訊息給其它物件,以及執行時如何獲取物件資訊

[2] 與Runtime System互動

Objective-C程式和Runtime System在三個不同層次進行互動:通過Objective-C原始碼;通過NSObject定義的函式;以及通過直接呼叫runtime functions

通常來講,Runtime System都是在幕後工作,我們需要做的就是編寫Objective-C程式碼,然後編譯。編譯器會為我們建立相應的資料結構和函式呼叫來實現語言的動態特性。這些資料結構儲存著在類、Category定義和Protocol宣告中所能找到的資訊,包括成員變數模板、selectors,以及其它從原始碼中提取到的資訊。

Runtime System是一個動態共享庫,位於/usr/include/objc,擁有一套公共的介面,由一系列函式和資料結構組成。開發人員可以使用純C呼叫一些函式來做編譯器做的事情,或者擴充套件Runtime System,為開發環境製作一些工具等等。儘管一般情況下,編寫Objective-C並不需要了解這些內容,但有時候會很有用。所有的函式都在Objective-C Runtime Reference有文件化資訊。

Cocoa中大部分物件都是NSObject的子類(NSProxy是一個例外),繼承了NSObject的方法。因此在這個繼承體系中,子類可以根據需求重新實現NSObject定義的一些函式,實現多型和動態性,比如description方法(返回描述自身的字串,類似Python中開頭的三引號)。

一些NSObject定義的方法只是簡單地詢問Runtime System獲得資訊,使得物件可以進行自省(introspection),比如用來確定類型別的isKindOfClass:,確定物件在繼承體系中的位置的isMemberOfClass:,判斷一個物件是否能接收某個特定訊息的respondsToSelector:,判斷一個物件是否遵循某個協議的conformsToProtocol:,以及提供方法實現地址的methodForSelector:。這些方法讓一個物件可以進行自省(introspect about itself)。

最主要的Runtime函式是用來發送訊息的,它由原始碼中的訊息表示式激發。傳送訊息是Objective-C程式中最經常出現的表示式,而該表示式最終會被轉換成objc_msgSend函式呼叫。比如一個訊息表示式[receiver message]會被轉換成objc_msgSend(receiver, selector),如果有引數則為objc_msgSend(receiver, selector, arg1, arg2, …)

訊息只有到執行時才會和函式實現繫結起來:首先objc_msgSend在receiver中查詢selector對應的函式實現;然後呼叫函式過程,將receiving object(即this指標)和引數傳遞過去;最後,返回函式的返回值。

傳送訊息的關鍵是編譯器為類和物件建立的結構,包含兩個主要元素,一個是指向superclass的指標,另一個是類的dispatch table,該dispatch table中的表項將selector和對應的函式入口地址關聯起來。

當一個物件被建立時,記憶體佈局中的第一個元素是指向類結構的指標,isa。通過isa指標,一個物件可以訪問它的類結構,進而訪問繼承的類結構。示例圖可參見此處

當向一個物件傳送訊息時,objc_msgSend先通過isa指標在類的dispatch table中查詢對應selector的函式入口地址,如果沒有找到,則沿著class hierarchy(類的繼承體系)尋找,直到NSObject類。這就是在執行時選擇函式實現,用OOP的行話來說,就是動態繫結。

為了加速傳送訊息的速度,Runtime System為每個類建立了一個cache,用來快取selector和對應函式入口地址的對映。

當objc_msgSend找到對應的函式實現時,它除了傳遞函式引數,還傳遞了兩個隱藏引數:receiving objectselector。之所以稱之為隱藏引數,是因為這兩個引數在原始碼中沒有顯示宣告,但還是可以通過self和_cmd來訪問。

當一個訊息要被髮送給某個物件很多次的時候,可以直接使用methodForSelector:來進行優化,比如下述程式碼:

  1. //////////////////////////////////////////////////////////////
  2. void (*setter)(id, SEL, BOOL);  
  3. int i;  
  4. setter = (void (*)(id, SEL, BOOL))[target  
  5.      methodForSelector:@selector(setFilled:)];  
  6. for ( i = 0; i < 1000, i++ )   
  7.      setter(targetList[i], @selector(setFilled:), YES);  
  8. //////////////////////////////////////////////////////////////

其中,methodForSelector:是由Cocoa Runtime System提供的,而不是Objective-C本身的語言特性。這裡需要注意轉換過程中函式型別的正確性,包括返回值和引數,而且這裡的前兩個引數需要顯示宣告為id和SEL。

[3] 方法的動態決議

有時候我們想要為一個方法動態地提供實現,比如Objective-C的@dynamic指示符,它告訴編譯器與屬性對應的方法是動態提供的。我們可以利用resolveInstanceMethod:resolveClassMethod:分別為物件方法和類方法提供動態實現。

一個Objective-C方法本質上是一個擁有至少兩個引數(self和_cmd)的C函式,我們可以利用class_addMethod向一個類新增一個方法。比如對於下面的函式:

  1. //////////////////////////////////////////////////////////////
  2. void dynamicMethodIMP(id self, SEL _cmd) {  
  3.      // implementation ….
  4. }  
  5. //////////////////////////////////////////////////////////////

我們可以利用resolveInstanceMethod:將它新增成一個方法(比如叫resolveThisMethodDynamically):

  1. //////////////////////////////////////////////////////////////
  2. @implementation MyClass  
  3. + (BOOL)resolveInstanceMethod:(SEL)aSEL  
  4. {  
  5.      if (aSEL == @selector(resolveThisMethodDynamically)) {  
  6.           class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "[email protected]:");  
  7.           return YES;  
  8.      }  
  9.      return [super resolveInstanceMethod:aSEL];  
  10. }  
  11. @end  
  12. //////////////////////////////////////////////////////////////

動態決議和傳送訊息並不衝突,在訊息機制起作用之前,一個類是有機會動態決議一個方法的。當respondsToSelector:或者instancesRespondToSelector:被啟用時,dynamic method resolver會優先有個機會為這個selector提供一份實現。如果實現了resolveInstanceMethod:,對於不想動態決議而想讓其遵循訊息轉發機制的selectors,返回NO即可。

Objective-C程式可以在執行時連結新的類和category。動態載入可以用來做很多不同的事情,比如System Preferences裡頭各種模組就是動態載入的。儘管有執行時函式可以動態載入Objective-C模組(objc/objc-load.h中的objc_loadModules),但Cocoa的NSBundle類提供了更方便的動態載入介面。

[4] 訊息轉發

向一個物件傳送它不處理的訊息是一個錯誤,不過在報錯之前,Runtime System給了接收物件第二次的機會來處理訊息。在這種情況下,Runtime System會向物件發一個訊息,forwardInvocation:,這個訊息只攜帶一個NSInvocation物件作為引數——這個NSInvocation物件包裝了原始訊息和相應引數。

通過實現forwardInvocation:方法(繼承於NSObject),可以給不響應的訊息一個預設處理方式。正如方法名一樣,通常的處理方式就是轉發該訊息給另一個物件:

  1. //////////////////////////////////////////////////////////////
  2. - (void)forwardInvocation:(NSInvocation *)anInvocation  
  3. {  
  4.      if ([someOtherObject respondsToSelector:[anInvocation selector]])  
  5.           [anInvocation invokeWithTarget:someOtherObject];  
  6.      else
  7.           [super forwardInvocation:anInvocation];  
  8. }  
  9. //////////////////////////////////////////////////////////////

對於不識別的訊息(在dispatch table中找不到),forwardInvocation:就像一箇中轉站,想繼續投遞或者停止不處理,都由開發人員決定。

[5] 型別編碼

為了支援Runtime System,編譯器將返回值型別、引數型別進行編碼,相應的編譯器指示符是@encode

比如,void編碼為v,char編碼為c,物件編碼為@,類編碼為#,選擇符編碼為:,而符合型別則由基本型別組成,比如

  1. typedefstruct example {  
  2.      id     anObject;  
  3.      char *aString;  
  4.      int anInt;  
  5. } Example;  

編碼為{[email protected]*i}。

[6] 屬性宣告

當編譯器遇到屬性宣告時,它會生成一些可描述的元資料(metadata),將其與相應的類、category和協議關聯起來。存在一些函式可以通過名稱在類或者協議中查詢這些metadata,通過這些函式,我們可以獲得編碼後的屬性型別(字串),複製屬性的attribute列表(C字串陣列)。因此,每個類和協議的屬性列表我們都可以獲得。

與型別編碼類似,屬性型別也有相應的編碼方案,比如readonly編碼為R,copy編碼為C,retain編碼為&等。

通過property_getAttributes函式可以後去編碼後的字串,該字串以T開頭,緊接@encode type和逗號,接著以V和變數名結尾。比如:

  1. @property char charDefault;  

描述為:Tc,VcharDefault

  1. @property(retain)ididRetain;  

描述為:[email protected],&,VidRetain

Property結構體定義了一個指向屬性描述符的不透明控制代碼:typedef struct objc_property *Property;

通過class_copyPropertyList和protocol_copyPropertyList函式可以獲取相應的屬性陣列:

  1. objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)  
  2. objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)  

通過property_getName函式可以獲取屬性名稱。

通過class_getProperty和protocol_getProperty可以相應地根據給定名稱獲取到屬性引用:

  1. objc_property_t class_getProperty(Class cls, constchar *name)  
  2. objc_property_t protocol_getProperty(Protocol *proto, constchar *name, BOOL isRequiredProperty, BOOL isInstanceProperty)  

通過property_getAttributes函式可以獲取屬性的@encode type string:

const char *property_getAttributes(objc_property_t property)

以上函式組合成一段示例程式碼:

  1. @interface Lender : NSObject {  
  2.      float alone;  
  3. }  
  4. @property float alone;  
  5. @end  
  6. id LenderClass = objc_getClass("Lender");  
  7. unsigned int outCount, i;  
  8. objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);  
  9. for (i = 0; i < outCount; i++) {  
  10.      objc_property_t property = properties[i];  
  11.      fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));  
  12. }  

相關推薦

[執行]Objective-C執行程式設計Runtime Programming

以前還真沒了解過Objective-C的執行時程式設計(Runtime Programming),今天特意在網上搜了下,原來這麼深奧啊 表示現在理解不了,先轉走了再說,之前轉載的文章都是大神們總結的綜合,轉載地址忘記註明了 ,抱歉。 --  [1] 版本和平臺   -

iOS開發之執行程式設計Runtime Programming淺讀

  什麼是執行時(Objective-C runtime)?       簡單的來說,Objective-C runtime是一個實現 Objective-C語言的庫。物件可以用C語言的結構體表示,而方法(methods) 可以用C函式實現。       事實上,他們也差不

C++ 泛型程式設計單詞數

題目描述 lily的好朋友xiaoou333最近很空,他想了一件沒有什麼意義的事情,就是統計一篇文章裡不同單詞的總數。下面你的任務是幫助xiaoou333解決這個問題。 輸入 有多組資料,每組一行,每組就是一篇小文章。每篇小文章都是由小寫字母和空格組成,沒有標點符號,每

C++ 泛型程式設計國名排序

題目描述 問題描述:小李在準備明天的廣交會,明天有來自世界各國的客房跟他們談生意,小李要儘快的整理出名單給經理,你能幫他把客戶來自的國家按英文字典次序排好嗎? 例如小李手上有來自加拿大,美國,中國的名單,排好的名單應是美國,加拿大,中國 。 輸入 第1行為一個n(n&

Objective-C屬性詳解附程式碼

宣告 在Objective-C中,有兩種宣告變數的方法,第一種直接宣告: @interface Person : NSObject { NSString *name; NSString *sex; NSString *age;

C語言基礎程式設計switch語句

給出一百分制成績,要求輸出成績等級‘A’、’B’、‘C’、‘D’、‘E’。90分以上為‘A’ ,80~89分為’B’ ,70~79分為‘C’... #include<stdio.h> int main() { int gra; printf("ple

動態程式設計Dynamic Programming

本文素材來自視訊,請自備梯子觀看:What Is Dynamic Programming and How To Use It Dynamic Programming:動態程式設計分為如下幾步: 將複雜問題拆分成多個較簡單的子問題 對每個子問題只計算一次,然後使用資料結構(陣列,字典等)

full-speed-python習題解答--非同步程式設計Asynchronous programming

  Exercises with asyncio 1. Implement an asynchronous coroutine function to add two variables and sleep for the duration of the sum. Use the as

Java 平臺反應式程式設計Reactive Programming入門

最近的一段時間裡,反應式程式設計在社群中得到了很大的關注。從Java社群來說,Java 9把反應式流規範以java.util.concurrent.Flow 類的形式新增到了標準庫中。Spring 5 已經支援了反應式程式設計實踐,並提供了 WebFlux 這樣的 Web 程

Objective C執行runtime

前言: Objective C的runtime技術功能非常強大,能夠在執行時獲取並修改類的各種資訊,包括獲取方法列表、屬性列表、變數列表,修改方法、屬性,增加方法,屬性等等,本文對相關的幾個要點做了一個小結。 目錄:   (6) 總結 (1)在執行時對函式進行動態替換 : 

Objective C執行runtime技術的幾個要點總結

前言: Objective C的runtime技術功能非常強大,能夠在執行時獲取並修改類的各種資訊,包括獲取方法列表、屬性列表、變數列表,修改方法、屬性,增加方法,屬性等等,本文對相關的幾個要點做了一個小結。 目錄:   (6) 總結 (1)在執行時對函式進行動態替換 : cl

Objective-C 執行程式設計指南 之 Type Encodings

為了幫助執行時系統,編譯器將每個方法的返回值型別和引數型別編碼成了字串,並把字串與方法選擇器關聯起來。 它使用的編碼方案在其他情況下也是有用的,因此該方案使用 @encode() 編譯器指令設定成了公共可用的。當給定一個型別說明, @encode() 會返回這個

關於執行異常Runtime Exception和受檢查的異常(Checked Exception)以及系統異常和普通異常的一些總結

Exception 表示程式還能夠克服和恢復的問題,Exception 類又分為執行時異常(Runtime Exception)和受檢查的異常(Checked Exception),所謂執行時異常就是開發人員編寫程式碼時不會報紅,但是執行不得當的話會執行出錯,也

VS的執行Runtime lIB

在開發window程式是經常會遇到編譯好好的程式拿到另一臺機器上面無法執行的情況,這一般是由於另一臺機器上面沒有安裝響應的執行時庫導致的,那麼這個與編譯選項MT、MTd、MD、MDd有什麼關係呢?這是msdn上面的解釋: MT:mutithread,多執行緒庫

c++多執行緒模式下的socket程式設計執行緒池實現

     socket 程式設計可以說是一個基本的技術掌握,而多個客戶端向服務端傳送請求又是一個非常常見的場景,因此多執行緒模式下的socket程式設計則顯得尤為常見與重要。     本文主要利用執行緒池的技術,來實現多執行緒的模式,執行緒池的優點就不多述了,相信大家都能理

Objective-C基礎筆記整理執行緒篇

多執行緒 1、基礎概念 程序:在系統中正在執行的一個應用程式,例如開啟常用的一個軟體,系統會啟動一個程序,每個執行緒之間是相互獨立的。 執行緒:一個程序要想執行任務,必須

C++11併發程式設計——初始C++11多執行緒庫

1 前言   C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。   在之前我們主要使用的多執行緒庫要麼

設定執行使用正式釋出的keystoreGradle配置

1、進入Project Structure:新增config,配置好keystore資訊。 2、Build Types ,將Signing Config 選擇為我們的config,為debug和release都配置好 3、點選0K ,搞定了! 這樣就可以使用正式版的k

【學習筆記】關於FbxSdk執行連結庫lib的選擇VS2010/VS2017

在使用Window開發Fbx外掛時,需要用到FbxSdk。我安裝在目錄:C:\Program Files\Autodesk\FBX\FBX SDK\2015.1\目前來說2015.1這個版本比較穩定。在使用VS2010以及VS2017時,需要引入它的執行時庫。在安裝目錄下有三

Objective-C 執行AppleScript腳本

url alloc use 文件中 path str lee nss app 在Objective-C裏事實上也能夠執行AppleScript 第一種方式是Source 將腳本寫到變量字符串裏 NSAppleEventDescriptor *eventDescr