runtime--小白看過來

来源:http://www.cnblogs.com/code-le/archive/2017/06/24/7073895.html
-Advertisement-
Play Games

目錄 RunTime 概述 RunTime消息機制 RunTime交換方法 RunTime消息轉發 RunTime關聯對象 RunTime實現字典與模型互轉 1.RunTime 概述 我們在面試的時候,經常都會被問到這麼個問題:為什麼說OC是一門動態的語言???其實也就是想知道你對runtime的了 ...


 

           目錄

  • RunTime 概述
  • RunTime消息機制
  • RunTime交換方法
  • RunTime消息轉發
  • RunTime關聯對象
  • RunTime實現字典與模型互轉

1.RunTime 概述

      我們在面試的時候,經常都會被問到這麼個問題:為什麼說OC是一門動態的語言???其實也就是想知道你對runtime的瞭解程度。

•Objc是一門動態語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運行時。也就是說只有編譯器是不夠的,還需要一個運行時系統 (runtime system) 來執行編譯後的代碼。這就是 Objective-C Runtime 系統存在的意義,它是整個Objc運行框架的一塊基石。 •RunTime簡稱運行時。OC就是運行時機制,其中最主要的是消息機制。對於C語言,函數的調用在編譯的時候會決定調用哪個函數。對於OC的函數,屬於動態調用過程,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。 •Runtime基本是用C和彙編寫的,並且它是開源代碼,由蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。 •使用運行時,需要導入頭文件 #import <objc/runtime.h>,並且xcode5之後,蘋果不建議使用底層方法,如果想要使用運行時,需要關閉嚴格檢查objc_msgSend的調用,BuildSetting->搜索msg 改為NO,否則主動調objc_msgSend函數會報錯。  

2.RunTime消息機制

1.消息機制是運行時裡面最重要的機制,OC中任何方法的調用,本質都是發送消息。eg:

當我們實例化這個對象時:MyClass *object = [[MyClass alloc] init];  就會調這個實例化方法:[object showUserName];

我們大概來看一下它的底層實現:

•在編譯階段,[object showUserName] 會被編譯器轉化為: objc_msgSend(object, "showUserName"),相當於一種“發消息”的行為。 • 在運行階段,執行到上述的objc_msgSend這個函數時,函數內部會到object對應的記憶體地址,尋找showUserName這個方法的地址並執行。如果找不到,就會拋一個“unknown selector sent to instance”的異常。  

證明過程:

在終端用命令打開此類文件所在的文件夾,繼續寫入命令:clang -rewrite-objc MyClass.m(把oc代碼轉寫成

c/c++代碼,我們常用它來窺探OC的底層實現),不一會在原來的同一目錄下會多出一個 MyClass.cpp 文件

雙擊打開,可以看到 init 方法已經被編譯器轉化為下麵這樣:

objc_msgSend 函數被定義在 objc/message.h 目錄下,其函數原型是:

OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )

該函數有兩個參數,一個 id 類型(消息接收對象),一個 SEL 類型(方法的selector)。

@selector (SEL):是一個SEL方法選擇器。SEL其主要作用是快速的通過方法名字查找到對應方法的函數指針,然後調用其函數。SEL其本身是一個Int類型的地址,地址中存放著方法的名字。

對於一個類中,每一個方法對應著一個SEL。所以一個類中不能存在2個名稱相同的方法(有歧義。。。),即使參數類型不同,因為SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。

歧義解釋:- (void)go {}       + (void)go {} 這兩個方法可以共存(我們知道,這兩個方法的名字都是go)。

我個人的理解是:當我們向一個對象或一個類發送消息時,runtime都會根據方法名去這個對象所屬的這個類的方法列表中查找方法,而方法列表的外層應該是一個字典,根據所傳的接收消息對象不同,查找的方法列表也不同。

objc_msgSend([MyClass class], @selector(go));

objc_msgSend([[MyClass alloc] init], @selector(go));

 

Id :是一個結構體指針類型,它可以指向 Objective-C 中的任何對象。

Class vclass = NSClassFromString(@"ViewController");

id vc = [[vclass alloc] init];

 

objc_object 結構體定義如下:

struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};

這就是我們通常所說的對象,這個結構體只有一個成員變數 isa,對象可以通過 isa 指針找到其所屬的類。isa 是一個 Class 類型的成員變數,那麼 Class 又是什麼呢?如下:

•·Class 也有一個 isa 指針,指向其所屬的元類(meta)。 •·super_class:指向其超類。 •·name:是類名。 •·version:是類的版本信息。 •·info:是類的詳情。 •·instance_size:是該類的實例對象的大小。 •·ivars:指向該類的成員變數列表。 •·methodLists:指向該類的實例方法列表,它將方法選擇器和方法實現地址聯繫起來。methodLists 是指向 ·objc_method_list 指針的指針,也就是說可以動態修改 *methodLists 的值來添加成員方法,這也是 Category 實現的原理,同樣解釋了 Category 不能添加屬性的原因。 •·cache:Runtime 系統會把被調用的方法存到 cache 中(理論上講一個方法如果被調用,那麼它有可能今後還會被調用),下次查找的時候效率更高。 •·protocols:指向該類的協議列表。

 

經過以上的講述,我們大概可以瞭解到,當調用一個方法時,其運行過程大致如下:

•底層調用 [p performSelector:@selector(eat)] 方法,編譯器在將代碼轉化為 objc_msgSend(p, @selector(eat))把方法的調用者和方法選擇器當做參數傳遞過去。 •首先,檢測這個 selector 的 target 是不是 nil,Objc 允許我們對一個 nil 對象執行任何方法不會 Crash,因為運行時會被忽略掉。 •如果target != nil,方法的調用者會通過 isa 指針來找到其所屬的類,然後在 cache 中查找該方法,找得到就跳到對應的方法去執行。 •若 cache 中未找到,再去 methodList 中查找。若能找到,則將 method 加入到 cache 中,以方便下次查找,並通過 method 中的函數指針跳轉到對應的函數中去執行。 •若 methodlist 中未找到,則去 superClass 中查找,一直找到 NSObject 類為止。若能找到,則將 method 加入到 cache 中。 •如果還是查不到,則會執行消息轉發,拋出異常。   我們來看看底層調用方法的幾個實例(分別是無參無返回,有參無返回,有參有返回):  

3.RunTime交換方法

應用場景:當系統自帶的方法功能不夠,需要給系統自帶的方法擴展一些功能時。

eg:實現image添加圖片的時候,自動判斷image是否為空,如果為空則提醒圖片不存在。

有以下三種比較好的解決方法:

1.自定義類, 重寫系統自帶的imageName:方法,這種方法雖然可以實現,但是它的弊端就是必須要使用自己的類,依賴性強。

2.給UIImage添加一個分類, 改變系統類的實現,給系統的類添加方法的時候調用(每次使用都需要導入頭文件,並且如果項目比較大,之前使用的方法全部需要更改)。

3.使用runtime的交互方法,給系統的方法添加功能. 具體實現 : 添加一個分類 --> 在分類中提供一個自定義判空增加新功能的方法 --> 將這個方法的實現和系統自帶的方法的實現交互.

交換方法的本質其實是交換兩個方法的實現,即調換le_imageNamed:和imageName:方法,達到調用le_imageNamed:其實就是調用imageNamed:方法的目的。

那麼首先需要明白方法在哪裡交換,因為以後都是使用自己定義的方法取代系統的方法,所以,當程式一啟動,就要求能使用自己定義的功能方法。我們一般在 + (void)load 方法里實現交換方法 (當程式啟動的時候就會調用該方法,換句話說,只要程式一啟動就會調用load方法,整個程式運行中只會調用一次)

•註意:交換方法時 le_imageNamed:方法中就不能再調用imageNamed:方法了,因為調用imageNamed:方法實質上相當於調用 le_imageNamed:方法,會迴圈引用造成死迴圈。

 

4.RunTime消息轉發

在方法調用的時候,如果沒有找到方法就會轉向消息轉發(攔截調用)。
攔截調用是指,在找不到調用的方法程式崩潰之前,你有機會通過重寫NSObject的五個方法來處理。

+ (BOOL)resolveClassMethod:(SEL)sel;

+ (BOOL)resolveInstanceMethod:(SEL)sel;

- (id)forwardingTargetForSelector:(SEL)aSelector;

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

- (void)forwardInvocation:(NSInvocation *)anInvocation;

  • 第一個方法是當你調用一個不存在的類方法的時候,會調用這個方法,預設返回NO,你可以加上自己的處理然後返回YES。
  • 第二個方法和第一個方法相似,只不過處理的是實例方法。
  • 第三個方法是將你調用的不存在的方法重定向到一個其他聲明瞭這個方法的類,只需要你返回一個有這個方法的target。
  • 第四個方法是獲取方法簽名進入下一步,進行消息轉發
  • 第五個方法將你調用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理後,調用invokeWithTarget:方法讓某個target觸發這個方法。

eg:   Monkey *monkey = [[Monkey alloc] init];

       ((void (*) (id, SEL)) objc_msgSend) (monkey, sel_registerName("fly"));  (猴子是不可能有飛的天賦的,除非它是孫猴子。。。)

 

"v@:"的含義請看官方文檔:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

消息轉發的作用主要是處理異常,下麵來看一個實例:

NSMutableArray *arrM = [NSMutableArray array];

 NSString *str = @"”;

[arrM addObject:str];   這行代碼會導致程式崩潰,因為數組添加的對象不能為空。解決方法:

總結:可以利用category + runtime + 異常的捕獲寫一個防止崩潰的框架,已經有大神在做了,具體請看:https://github.com/chenfanfang/AvoidCrash

 

5.RunTime關聯對象

應用場景:當你準備用一個系統的類或者是你寫的類,但是這個類並不能滿足你的需求,你需要額外添加一個屬性。
一般解決辦法要麼是extends(繼承),要麼使用category(類別)。
但基本不推薦使用extends,主要是耦合性太強,主要使用category。
我們都知道,分類中是無法設置屬性的,如果在分類的聲明中寫@property 只能為其生成get 和 set 方法的聲明, 這時候,runtime的關聯屬性就能發揮它的作用了。

註意:使用property的時候,同時重寫set get方法會報錯。覆寫了get和set方法之後@property預設生成的@synthesize就不會起作用了,這也就意味著你的類不會自動生成出來實例變數了,你就必須要自己聲明實例變數。

設置關聯對象使用

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

•·object 是源對象 •·value 是被關聯的對象 •·key 是關聯的鍵,objc_getAssociatedObject 方法通過不同的 key 即可取出對應的被關聯對象 •·policy 是一個枚舉值,表示關聯對象的行為,從命名就能看出各個枚舉值的含義  

獲取關聯對象使用

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)

要刪除某一個被關聯的對象,使用 objc_setAssociatedObject 方法將對應的 key 設置成 nil 即可。

移除源對象中所有的關聯對象

OBJC_EXPORT void objc_removeAssociatedObjects(id object) 

 

eg:為view類添加一個點擊手勢(主要實現它的子類(UILabel 、UIImageView可以像button一樣,有自己的點擊方法)

 

 

6.RunTime實現字典與模型互轉

其中,獲取屬性的方法可以讓我們拿到各個類的私有屬性,讓後利用kvc賦值,加以應用。

歸檔解檔:

常規的歸檔解檔方法,可是當model的屬性很多時,這樣寫就有點尷尬了。。。

 

 利用runtime實現批量歸檔解檔:

 

我們在使用一些json轉model的第三方框架(JSONModel,MJExtension等)時,它們的底層都是利用runtime去實現的:

字典轉模型的時候:

1.根據字典的 key 生成 setter 方法

2.使用 objc_msgSend 調用 setter 方法為 Model 的屬性賦值(或者 KVC)

 

模型轉字典的時候:

1.調用 class_copyPropertyList 方法獲取當前 Model 的所有屬性

2.調用 property_getName 獲取屬性名稱

3.根據屬性名稱生成 getter 方法

4.使用 objc_msgSend 調用 getter 方法獲取屬性值(或者 KVC)

 

感謝這幾個篇文章對我的幫助:

http://www.cocoachina.com/ios/20160523/16386.html

http://www.jianshu.com/p/5d625f86bd02

由於自己之前查閱資料時,沒有及時寫總結,現在有點閑時了才開始寫,所以有些參考文章都不記得了,請相關作者見諒。。。

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CSS實現兩端對齊效果 兩端對齊,從概念上來說,其實不難理解。如果不明白什麼叫兩端對齊,可以玩玩word等辦公軟體。 下麵談談如何實現文本的兩端對齊。我所知道的大概有以下幾種方法 text align "w3school" 指出,text align用於設置塊級元素內文本的水平對齊方式。如果想使in ...
  • <img src="https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=javascript%E6%91%98%E8%A6%81%E5%9B%BE%E6%A0%87&step_word=&hs=2&pn=36&spn=0... ...
  • 本來上個月就像發的,但是一直忙啊忙的也沒時間整理,所以拖到了現在。 好吧上面這句就是廢話,我就是感概下。下麵是正文。 前段時間在弄一個輕量級的web項目,要構建一個樹狀結構目錄,同時希望能把部分選中的狀態給表現出來。項目中只用了jquery,個人也不想再引入一些其他的js框架或者插件,一個是考慮到界 ...
  • 記錄JavaScript練習筆記和知識點整理的第二天,遇到在程式里使用等差數列求和公式的操作!遇到使用正則表達式判斷數組中是否有重覆的值!又學會了新操作。 ...
  • var arr1 = [1,2,3,4,5,6]; arr1[0]; arr1[1]; console.log("長度:"+arr1.length); 一、遍曆數組 / * i=下標*/ for(var i=0;i<arr1.length;i++){ console.log(arr1[i]); } ...
  • Es6中如何使用splic,delete等數組刪除方法 1:js中的splice方法 splice(index,len,[item]) 註釋:該方法會改變原始數組。 splice有3個參數,它也可以用來替換/刪除/添加數組內某一個或者幾個值 index:數組開始下標 len: 替換/刪除的長度 it ...
  • 在《vue-cli搭建的項目中增加後臺mock介面》中實現了後臺mock,但是前端post的t數據都要在mock的後臺介面中使用req的接收數據事件獲取http協議body中的數據。 如果前端需要使用cookie,後端要讀取,那麼在後臺mock的介面中還要獲取req的headers,並從中取得coo ...
  • 為 device node 取 label name, 可以在其它位置使用 &label 存取 device node。 Ex ./arch/arm/boot/dts/stm32f429.dtsi ./arch/arm/boot/dts/stm32f429 disco.dts ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...