Runtime

Runtime

概念

Runtime是一套底層純C語言API,OC代碼最終都會被編譯器轉化為運行時代碼,通過消息機制決定函數調用方式,這也是OC作為動態語言使用的基礎。Runtime的最大特征就是實現了OC語言的動態特性。

消息機制原理

在Object-C的語言中,對象方法調用都是類似[receiver selector] 的形式,其本質:就是讓對象在運行時發送消息的過程。

而方法調用[receiver selector] 分為兩個過程:

  • 編譯階段

[receiver selector] 方法被編譯器轉化,分為兩種情況:

  1. 不帶參數的方法被編譯為:objc_msgSend(receiver,selector)
  2. 帶參數的方法被編譯為:objc_msgSend(recevier,selector,org1,org2,…)
  • 運行時階段

消息接收者recever尋找對應的selector,也分為兩種情況:

  1. 接收者能找到對應的selector,直接執行接收receiver對象的selector方法。
  2. 接收者找不到對應的selector,消息被轉發或者臨時向接收者添加這個selector對應的實現內容,否則崩潰

總而言之:

OC調用方法[receiver selector],編譯階段確定了要向哪個接收者發送message消息,但是接收者如何響應決定于運行時的判斷。

重要概念

objc_msgSend

所有 Objective-C 方法調用在編譯時都會轉化為對 C 函數 objc_msgSend 的調用。objc_msgSend(receiver,selector); 是 [receiver selector]; 對應的 C 函數。

Object(對象)

objc/runtime.h 中Object(對象) 被定義為指向 objc_object 結構體 的指針,objc_object結構體 的數據結構如下:

//runtime對objc_object結構體的定義
struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};//id是一個指向objc_object結構體的指針,即在Runtime中:
typedef struct objc_object *id;//OC中的對象雖然沒有明顯的使用指針,但是在OC代碼被編譯轉化為C之后,每個OC對象其實都是擁有一個isa(指向對象的類)的指針的

Class(類)

objc/runtime.h 中Class(類) 被定義為指向 objc_class 結構體 的指針,objc_class結構體 的數據結構如下:

//runtime對objc_class結構體的定義
struct objc_class {Class _Nonnull isa;                                          // objc_class 結構體的實例指針#if !__OBJC2__Class _Nullable super_class;                                 // 指向父類的指針const char * _Nonnull name;                                  // 類的名字long version;                                                // 類的版本信息,默認為 0long info;                                                   // 類的信息,供運行期使用的一些位標識long instance_size;                                          // 該類的實例變量大小;struct objc_ivar_list * _Nullable ivars;                     // 該類的實例變量列表struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定義的列表struct objc_cache * _Nonnull cache;                          // 方法緩存struct objc_protocol_list * _Nullable protocols;             // 遵守的協議列表
#endif};//class是一個指向objc_class結構體的指針,即在Runtime中:
typedef struct objc_class *Class; 

SEL (方法選擇器)

typedef struct objc_selector *SEL;//Objective-C在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL

1.不同類中相同名字的方法對應的方法選擇器是相同的。
2.即使是同一個類中,方法名相同而變量類型不同也會導致它們具有相同的方法選擇器。

獲取SEL有三種方法:

1.OC中,使用@selector(“方法名字符串”)
2.OC中,使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法,使用sel_registerName(“方法名字符串”)

Method(方法)

objc/runtime.h 中Method(方法) 被定義為指向 objc_method 結構體 的指針,在objct_class定義中看到methodLists,其中的元素就是Method,objc_method結構體 的數據結構如下:

struct objc_method {SEL _Nonnull method_name;                    // 方法名char * _Nullable method_types;               // 方法類型IMP _Nonnull method_imp;                     // 方法實現
};//Method表示某個方法的類型
typedef struct objc_method *Method;

Runtime消息轉發

動態方法解析:動態添加方法

Runtime足夠強大,能夠在運行時動態添加一個未實現的方法,這個功能主要有兩個應用場景:

1. 動態添加未實現方法,解決代碼中因為方法未找到而報錯的問題
2. 利用懶加載思路,若一個類有很多個方法,同時加載到內存中會耗費資源,可以使用動態解析添加方法

方法動態解析主要用到的方法如下:

//OC方法:
//類方法未找到時調起,可于此添加類方法實現
+ (BOOL)resolveClassMethod:(SEL)sel//實例方法未找到時調起,可于此添加實例方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel//Runtime方法:
/**運行時方法:向指定類中添加特定方法實現的操作@param cls 被添加方法的類@param name selector方法名@param imp 指向實現方法的函數指針@param types imp函數實現的返回值與參數類型@return 添加方法是否成功*/
BOOL class_addMethod(Class _Nullable cls,SEL _Nonnull name,IMP _Nonnull imp,const char * _Nullable types)
  • 解決方法無響應崩潰問題

執行OC方法其實就是一個發送消息的過程,若方法未實現,可以利用方法動態解析與消息轉發來避免程序崩潰,這主要涉及下面一個處理未實現消息的過程:

在這個過程中,可能還會使用到的方法有:

img

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface ViewController ()
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 執行 fun 函數[self performSelector:@selector(fun)];
}// 重寫 resolveInstanceMethod: 添加對象方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(fun)) { // 如果是執行 fun 函數,就動態解析,指定新的 IMPclass_addMethod([self class], sel, (IMP)funMethod, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}void funMethod(id obj, SEL _cmd) {NSLog(@"funMethod"); //新的 fun 函數
}
@end//日志輸出:2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] funMethod

從執行任務的輸出日志中,可以看到:

雖然沒有實現 fun 方法,但是通過重寫 resolveInstanceMethod: ,利用 class_addMethod 方法添加對象方法實現 funMethod 方法,并執行。從打印結果來看,成功調起了funMethod 方法。

消息接收者重定向

如果上一步中 +resolveInstanceMethod:或者 +resolveClassMethod: 沒有添加其他函數實現,運行時就會進行下一步:消息接受者重定向。

如果當前對象實現了 -forwardingTargetForSelector:Runtime 就會調用這個方法,允許將消息的接受者轉發給其他對象,其主要方法如下:

//重定向類方法的消息接收者,返回一個類
- (id)forwardingTargetForSelector:(SEL)aSelector//重定向實例方法的消息接受者,返回一個實例對象
- (id)forwardingTargetForSelector:(SEL)aSelector

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface Person : NSObject
- (void)fun;
@end@implementation Person- (void)fun {NSLog(@"fun");
}
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 執行 fun 方法[self performSelector:@selector(fun)];
}+ (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 為了進行下一步 消息接受者重定向
}// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(fun)) {return [[Person alloc] init];// 返回 Person 對象,讓 Person 對象接收這個消息}return [super forwardingTargetForSelector:aSelector];
}//日志輸出:2019-09-01 23:24:34.911774+0800 XKRuntimeKit[3064:521123] fun

從執行任務的輸出日志中,可以看到:

雖然當前 ViewController 沒有實現 fun 方法,+resolveInstanceMethod: 也沒有添加其他函數實現。
但是我們通過 forwardingTargetForSelector 把當前 ViewController 的方法轉發給了 Person 對象去執行了。

通過forwardingTargetForSelector 可以修改消息的接收者,該方法返回參數是一個對象,如果這個對象是不是 nil,也不是 self,系統會將運行的消息轉發給這個對象執行。否則,繼續進行下一步:消息重定向流程

消息重定向

如果經過消息動態解析、消息接受者重定向,Runtime 系統還是找不到相應的方法實現而無法響應消息,Runtime 系統會利用 -methodSignatureForSelector: 方法獲取函數的參數和返回值類型。

其過程:

  1. 如果 -methodSignatureForSelector: 返回了一個 NSMethodSignature 對象(函數簽名),Runtime 系統就會創建一個 NSInvocation 對象,
    并通過 -forwardInvocation: 消息通知當前對象,給予此次消息發送最后一次尋找 IMP(指向實現方法的函數指針) 的機會。
  2. 如果 -methodSignatureForSelector: 返回 nil。則 Runtime 系統會發出 -doesNotRecognizeSelector: 消息,程序也就崩潰了。

所以可以在-forwardInvocation:方法中對消息進行轉發。

其主要方法:

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;// 獲取函數的參數和返回值類型,返回簽名
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;

例子:

#import "ViewController.h"
#import <objc/runtime.h>@interface Person : NSObject
- (void)fun;
@end@implementation Person
- (void)fun {NSLog(@"fun");
}
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 執行 fun 函數[self performSelector:@selector(fun)];
}+ (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 為了進行下一步 消息接受者重定向
}- (id)forwardingTargetForSelector:(SEL)aSelector {return nil; // 為了進行下一步 消息重定向
}// 獲取函數的參數和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {return [NSMethodSignature signatureWithObjCTypes:"v@:"];}return [super methodSignatureForSelector:aSelector];
}// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {SEL sel = anInvocation.selector;   // 從 anInvocation 中獲取消息Person *p = [[Person alloc] init];if([p respondsToSelector:sel]) {   // 判斷 Person 對象方法是否可以響應 sel[anInvocation invokeWithTarget:p];  // 若可以響應,則將消息轉發給其他對象處理} else {[self doesNotRecognizeSelector:sel];  // 若仍然無法響應,則報錯:找不到響應方法}
}
@end//日志輸出:
2019-09-01 23:24:34.911774+0800 XKRuntimeKit[30032:8724248] fun

從執行任務的輸出日志中,可以看到:

在 -forwardInvocation: 方法里面讓 Person 對象去執行了 fun 函數

問:既然 -forwardingTargetForSelector:-forwardInvocation: 都可以將消息轉發給其他對象處理,那么兩者的區別在哪?

答:區別就在于 -forwardingTargetForSelector: 只能將消息轉發給一個對象。而 -forwardInvocation: 可以將消息轉發給多個對象。

Runtime的應用

動態方法交換

實現動態方法交換(Method Swizzling )是Runtime中最具盛名的應用場景,其原理是:

通過Runtime獲取到方法實現的地址,進而動態交換兩個方法的功能。

類目添加新的屬性

在日常開發過程中,常常會使用類目Category為一些已有的類擴展功能。雖然繼承也能夠為已有類增加新的方法,而且相比類目更是具有增加屬性的優勢,但是繼承畢竟是一個重量級的操作,添加不必要的繼承關系無疑增加了代碼的復雜度。

獲取類詳細屬性

  • 獲取屬性列表
  • 獲取所有成員變量
  • 獲取所有方法
  • 獲取當前遵循的所有協議

解決同一方法高頻率調用的效率問題

Runtime源碼中的IMP作為函數指針,指向方法的實現。通過它,可以繞開發送消息的過程來提高函數調用的效率。當需要持續大量重復調用某個方法的時候,會十分有用。

動態操作屬性

  • 修改私有屬性
  • 改進iOS歸檔和解檔
  • 實現字典與模型的轉換

利用Runtime實現的思路大體如下:

借助Runtime可以動態獲取成員列表的特性,遍歷模型中所有屬性,然后以獲取到的屬性名為key,在JSON字典中尋找對應的值value;再將每一個對應Value賦值給模型,就完成了字典轉模型的目的。

Swift中的Runtime

Swift是靜態語言,本身沒有動態特性。

結論:

  • 對于純Swift類來說,沒有動態特性。方法和屬性不加任何修飾符的情況下,這個時候已經不具備我們所謂的Runtime特性了。
  • 對于純Swift類,方法和屬性添加@objc標識的情況下,當前我們可以通過Runtime API拿到,但是在我們的OC中是沒辦法進行調度的。
  • 對于繼承自NSObject類來說,如果我們想要動態的獲取當前的屬性和方法,必須在其聲明前添加@objc關鍵字,方法交換需要添加 dynamic 標識,否則也是無法通過Runtime API獲取的。

反射

反射是Swift中動態獲取的一種方法,可以動態獲取類型、成員信息,在運行時可以調用方法、屬性等行為的特性。上面的結論說了對于一個純Swift類來說,并不支持像OC那樣操作,但是Swift標準庫依然提供了反射機制讓我們訪問成員信息。

用法如下:

import UIKit//下方OC的部分可以不加沒問題
class LGTeacher: NSObject{@objc var age: Int = 18@objc dynamic func teach(){print("teach")}
}let t = LGTeacher()let mirror = Mirror(reflecting: t.self)
for pro in mirror.children{print("\(pro.label):\(pro.value)")
}

運行結果:

Optional("age"):18

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/215498.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/215498.shtml
英文地址,請注明出處:http://en.pswp.cn/news/215498.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

代碼隨想錄27期|Python|Day13|棧與隊列|239. 滑動窗口最大值 (一刷至少需要理解思路)|347.前 K 個高頻元素 (一刷至少需要理解思路)

239. 滑動窗口最大值 單調隊列 滑動窗口中的隊列一直保持出口大&#xff0c;入口小的順序。&#xff08;圖&#xff1a;代碼隨想錄&#xff09; 1、每次有新的元素進入&#xff08;也就是滑動窗口移動后&#xff09;&#xff0c;都需要先和入口的元素比較大小&#xff0c;如果…

BDD100K數據集

官網:BDD100K (vis.xyz)????? 論文&#xff1a;[1805.04687] BDD100K: A Diverse Driving Dataset for Heterogeneous Multitask Learning (arxiv.org) github:bdd100k/bdd100k: Toolkit of BDD100K Dataset for Heterogeneous Multitask Learning - CVPR 2020 Oral Pap…

特發性震顫會導致其他并發癥嗎?

特發性震顫是一種較為常見的神經系統疾病&#xff0c;其主要癥狀是姿勢性震顫&#xff0c;常常在手部開始&#xff0c;并可逐漸累及頭部、下肢等其他部位。雖然特發性震顫的主要癥狀是震顫&#xff0c;但該病也可能導致其他并發癥。下面將詳細介紹特發性震顫可能引起的并發癥。…

靈茶 - 2023 - 12 - 12

鏈接 Problem - 620C - Codeforces 思路 : 貪心 : 對于每一段區間&#xff0c;從前往后貪&#xff0c;如果前面一段區間有重復數字&#xff0c;那么就直接合并成答案的一段區間&#xff0c;然后繼續尋找下一段區間&#xff0c;對于最后一段&#xff0c;如果沒有匹配的話&am…

自定義kafka客戶端消費topic

文章目錄 自定義kafka客戶端消費topic結論1 背景2 spring集成2.1.8.RELEASE版本不支持autoStartup屬性3 自定義kafka客戶端消費topic3.1 yml配置3.2 KafkaConfig客戶端配置3.3 手動啟動消費客戶端 自定義kafka客戶端消費topic 結論 使用自定義的KafkaConsumer給spring進行管理…

人體關鍵點檢測2:Pytorch實現人體關鍵點檢測(人體姿勢估計)含訓練代碼

人體關鍵點檢測2&#xff1a;Pytorch實現人體關鍵點檢測(人體姿勢估計)含訓練代碼 目錄 人體關鍵點檢測2&#xff1a;Pytorch實現人體關鍵點檢測(人體姿勢估計)含訓練代碼 1. 前言 2.人體關鍵點檢測方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下而上)方法&#xff1…

Android - 分區存儲 MediaStore、SAF

官方頁面 參考文章 一、概念 分區存儲&#xff08;Scoped Storage&#xff09;的推出是針對 APP 訪問外部存儲的行為&#xff08;亂建亂獲取文件和文件夾&#xff09;進行規范和限制&#xff0c;以減少混亂使得用戶能更好的控制自己的文件。 公有目錄被分為兩大類&#xff1a;…

會員運營常用的ChatGPT通用提示詞模板

會員體系&#xff1a;如何建立和完善會員體系&#xff1f; 會員等級&#xff1a;如何設定會員等級及權益&#xff1f; 會員留存&#xff1a;如何提高會員留存率&#xff1f; 會員活躍度&#xff1a;如何提高會員活躍度&#xff1f; 會員招募&#xff1a;如何招募新會員&…

ubuntu install sqlmap

refer: https://github.com/sqlmapproject/sqlmap 安裝sqlmap&#xff0c;可以直接使用git 克隆整個sqlmap項目&#xff1a; git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev 2.然后進入sqlmap-dev&#xff0c;使用命令&#xff1a; python s…

靜態代理IP搭建步驟,靜態匿名在線代理IP如何使用?

靜態代理搭建步驟 1. 確定需求 在搭建靜態代理之前&#xff0c;需要明確自己的需求&#xff0c;包括代理服務器的位置、訪問速度、匿名性、安全性等方面的要求。 2. 選擇代理服務器提供商 可以選擇自己購買服務器搭建代理&#xff0c;也可以選擇使用云服務提供商的代理服務…

【Python百寶箱】探索強化學習算法的利器:航行在AI之海的羅盤指南

強化學習的工具寶盒&#xff1a;探索各色瑰寶&#xff0c;點亮智能之旅 前言 人工智能和強化學習正成為推動科技進步的重要力量。在這個領域中&#xff0c;使用適當的庫和工具可以加速算法研發和應用部署的過程。本文將深入探索一系列具有代表性的強化學習庫和工具&#xff0…

有趣的數學 用示例來闡述什么是初值問題二

一、示例 解決以下初值問題。 解決這個初始值問題的第一步是找到一個通用的解決方案。為此&#xff0c;我們找到微分方程兩邊的反導數。 即 我們能夠對兩邊進行積分&#xff0c;因為y項是單獨出現的。請注意&#xff0c;有兩個積分常數&#xff1a;C1和C2。求解前面的方程y給出…

電工--半導體器件

目錄 半導體的導電特性 PN結及其單向導電性 二極管 穩壓二極管 雙極型晶體管 半導體的導電特性 本征半導體&#xff1a;完全純凈的、晶格完整的半導體 載流子&#xff1a;自由電子和空穴 溫度愈高&#xff0c;載流子數目愈多&#xff0c;導電性能就愈好 型半導體&…

28. Python Web 編程:Django 基礎教程

目錄 安裝使用創建項目啟動服務器創建數據庫創建應用創建模型設計路由設計視圖設計模版 安裝使用 Django 項目主頁&#xff1a;https://www.djangoproject.com 訪問官網 https://www.djangoproject.com/download/ 或者 https://github.com/django/django Windows 按住winR 輸…

docker build構建報錯:shim error: docker-runc not installed on system

問題&#xff1a; docker構建鏡像時報錯&#xff1a;shim error: docker-runc not installed on system 解決&#xff1a; ln -s /usr/libexec/docker/docker-runc-current /usr/bin/docker-runc

MySQL數據庫——鎖-表級鎖(表鎖、元數據鎖、意向鎖)

目錄 介紹 表鎖 語法 特點 元數據鎖 介紹 演示 意向鎖 介紹 分類 演示 介紹 表級鎖&#xff0c;每次操作鎖住整張表。鎖定粒度大&#xff0c;發生鎖沖突的概率最高&#xff0c;并發度最低。應用在MyISAM、InnoDB、BDB等存儲引擎中。 對于表級鎖&#xff0c;主要…

選擇排序和堆排序

目錄 前言 一.選擇排序 1.思想 2.實現 3.特點 二.堆排序 1.思想 2.實現 3.特點 前言 排序算法是計算機科學中的基礎工具之一&#xff0c;對于數據處理和算法設計有著深遠的影響。了解不同排序算法的特性和適用場景&#xff0c;能夠幫助程序員在特定情況下…

【Go】基于GoFiber從零開始搭建一個GoWeb后臺管理系統(一)搭建項目

前言 最近兩個月一直在忙公司的項目&#xff0c;上班時間經常高強度寫代碼&#xff0c;下班了只想躺著&#xff0c;沒心思再學習、做自己的項目了。最近這幾天輕松一點了&#xff0c;終于有時間 摸魚了 做自己的事了&#xff0c;所以到現在我總算是搭起來一個比較完整的后臺管…

nrfutil工具安裝

準備工作&#xff0c;下載相關安裝包 鏈接&#xff1a;https://pan.baidu.com/s/1LWxhibf8LiP_Cq3sw0kALQ 提取碼&#xff1a;2dlc 解壓后&#xff0c;分別安裝以下安裝包 在C盤下創建目錄nordic_tools&#xff0c;并將nrfutil復制到剛創建的目錄下 環境變量path下添加C:\nor…

圖像采集卡 Xtium?2-XGV PX8支持高速 GigE Vision 工業相機

圖像采集卡&#xff08;Image Capture Card&#xff09;&#xff0c;又稱圖像捕捉卡&#xff0c;是一種可以獲取數字化視頻圖像信息&#xff0c;并將其存儲和播放出來的硬件設備。很多圖像采集卡能在捕捉視頻信息的同時獲得伴音&#xff0c;使音頻部分和視頻部分在數字化時同步…