ObjectTive C語言語法,[譯]理解 Objective-C 運行時(下篇)

本文來自網易云社區

作者:宋申易

所以到底 objc_msgSend 發生了什么?

很多事情。看一下這段代碼:

[self printMessageWithString:@"Hello World!"];

這實際上被編譯器翻譯成:

objc_msgSend(self, @selector(printMessageWithString:), @"Hello World!");

我們順著目標對象的 isa 指針查找,看該對象(或者它其中一個父類)是否能響應 @selector(printMessageWithString:) 選擇器。假設我們在分派表(dispatch table)或者緩存中找到了該選擇器,我們會跟蹤函數指針并執行它。所以 objc_msgSend() 永遠不會返回,它開始執行,然后跟蹤一個指向你的方法的指針,然后你的方法返回,這看起來就像 objc_msgSend() 返回了一樣。

Bill Bumgarner 在(Part 1, Part 2 & Part 3)里描述了更多 objc_msgSend() 的細節。總結一下他的文章結合你看到的 Objective-C 運行時代碼:

檢查被忽略的選擇器和短路。顯然,如果我們在垃圾收集下運行,我們可以忽略 -retain,-release 等調用。

檢查 nil 目標。和其他語言不同,在 ObjC 里向 nil 發送消息十分合理并且有些情況下確實想要這么做。假如不是 nil 則繼續……

接下來在類中找到 IMP,首先通過類緩存來查找,如果找到就跟隨指針跳轉到對應的函數

如果在緩存中找不到 IMP,則通過分派表來查找,如果找到就跟隨指針跳轉到對應的函數

如果這兩個地方都找不到 IMP,則跳轉到轉發(forwarding)機制。

這意味著最終你的代碼會被編譯器轉譯成 C 函數。你寫的某個方法可能是這樣:

-(int)doComputeWithNum:(int)aNum

它會被轉換成……

int aClass_doComputeWithNum(aClass *self, SEL _cmd, int aNum)

ObjC 運行時會通過調用這些方法的函數指針來真正執行方法。我曾說過你不能直接調用這些轉譯后的方法,但其實 Cocoa 框架提供了一個獲取函數指針的方法……

// C function pointer

int (computeNum *)(id, SEL, int);

// methodForSelector is COCOA & not ObjC Runtime

// gets the same function pointer objc_msgSend gets

computeNum = (int (*)(id, SEL, int))[target methodForSelector:@selector(doComputeWithNum:)];

// execute the C function pointer returned by the runtime

computeNum(obj, @selector(doComputeWithNum:), aNum);

用這種方式你可以直接訪問函數并且直接在運行時中執行它,甚至繞過運行時的動態特性(為了確保指定的方法被執行)。ObjC 運行時也用這種方法來調用你的函數,只是用了 objc_msgSend()。

Objecetive-C 消息轉發

在 Objective-C 中,發送消息給可能不能響應該消息的對象是合法的(可能是有意設計的)。蘋果文檔里提到可能的原因一個是模擬 Objective-C 并不原生支持的多重繼承,或者是你想把真正接受消息的類或者對象隱藏起來。這也是運行時很有必要的一件事。具體是這樣的:

運行時搜索類緩存、類分派表以及父類的所有方法,沒有找到指定的方法。

運行時對你的類調用 + (BOOL)resolveInstanceMethod:(SEL)aSEL。這給你提供了一個方法實現的機會,告訴運行時你已經解決了這個方法,如果它應該開始進行搜索,它將會找到方法。具體你可以這樣做,定義一個函數:

void fooMethod(id obj, SEL _cmd) {

NSLog(@"Doing Foo");

}

然后可以使用 class_addMethod() 來解析它…

+ (BOOL)resolveInstanceMethod:(SEL)aSEL {

if(aSEL == @selector(doFoo:)) {

class_addMethod([self class],aSEL,(IMP)fooMethod,"v@:");

return YES;

}

return [super resolveInstanceMethod];

}

class_addMethod() 的最后一部分中的 v@: 是該方法返回的內容,也是它的參數。你可以在運行時指南的 Type Encodings 章節中了解可以放入哪些內容。

如果我們不能解析該方法,運行時會繼續調用 - (id)forwardingTargetForSelector:(SEL)aSelector。它所做的是給你一個機會,讓運行時指向在另一個可以響應消息的對象。最好在開銷更大的 - (void)forwardInvocation:(NSInvocation *)anInvocatio 方法接管之前調用,例如:

{

if(aSelector == @selector(mysteriousMethod:)) {

return alternateObject;

}

return [super forwardingTargetForSelector:aSelector];

}

顯然,你不想從這個方法中返回 self,因為這樣會導致無限循環。

運行時最后一次嘗試發送一個消息發送到它的預定目標,調用 - (void)forwardInvocation:(NSInvocation *)anInvocation。NSInvocation 本質上是一個 Objective-C 消息的的對象形式。一旦你有了一個 NSInvocation,你基本上可以改變任何信息,包括它的目標,選擇器和參數。例如你可以做:

- (void)forwardInvocation:(NSInvocation *)invocation {

SEL invSEL = invocation.selector;

if([altObject respondsToSelector:invSEL]) {

[invocation invokeWithTarget:altObject];

} else {

[self doesNotRecognizeSelector:invSEL];

}

}

如果你的對象繼承了 NSObject, 默認情況下 - (void)forwardInvocation:(NSInvocation *)anInvocation 實現會調用 -doesNotRecognizeSelector:方法。你可以重寫這個方法如果你想最后再做點什么。

不脆弱的(Non Fragile)實例變量列表(ivars) (現代運行時)

現代運行時新增加了不脆弱的(Non Fragile) ivars 的概念。當編譯你的類的時候,編譯器生成了一個實例變量內存布局(ivar layout),來告訴運行時去那里訪問你的類的實例變量們。這是一個底層實現細節:ivars 是實例變量分別相對于你的對象地址的偏移量,讀取 ivars 的字節數就是讀取的變量的大小。你的 ivar 布局可能看起來像這樣(第一列是字節偏移量):

da7d76c11064faec1e1aa05420d9bf9e.png

這里我們畫出了一個 NSObject 的實例變量內存布局。我們有一個繼承了 NSObject 的類,增加了一些新的實例變量。這沒什么問題,直到蘋果發布了新的 Mac OS X 10.x 系統,NSObject 突然增加兩個新的實例變量,于是:

280450f6ceb9c6c0df2a92d5beccd5d1.png

你的自定義對象和 NSObject 對象重疊的部分被清除。如果 Apple 永遠不改變之前的布局可以避免這種情況,但如果他們那樣做,那么他們的框架就永遠不會進步。在“脆弱的 ivars” 下,你必須重新編譯你從 Apple 繼承的類,來恢復兼容性。那么在不脆弱的情況下會發生什么呢?

2c7b648873003b6c98d76f4545bf3fcd.png

在不脆弱的 ivars 下,編譯器生成與脆弱 ivars 相同的 ivars 布局。然而,當運行時檢測到和父類有重疊時,它會調整偏移量,以增加對類的補充,保留了在子類中添加的內容。

Objective-C 關聯對象(Associated Objects)

Mac OS X 10.6 Snow Leopard 中引入了關聯引用。Objective-C 沒有原生支持動態地將變量添加到對象上。因此,你需要竭盡全力構建基礎架構,以假裝正在向類中添加一個變量。在 Mac OS X 10.6 中,Objective-C 運行時提供了原生支持。如果我們想給每個已經存在的類添加一個變量,比如 NSView,我們可以這樣做:

#import //Cocoa

#include //objc runtime api’s

@interface NSView (CustomAdditions)

@property(retain) NSImage *customImage;

@end

@implementation NSView (CustomAdditions)

static char img_key; //has a unique address (identifier)

- (NSImage *)customImage {

return objc_getAssociatedObject(self,&img_key);

}

- (void)setCustomImage:(NSImage *)image {

objc_setAssociatedObject(self,&img_key,image,

OBJC_ASSOCIATION_RETAIN);

}

@end

你可以在 runtime.h 看到。如何存儲傳遞給 objc_setAssociatedObject() 的值的選項:

/* Associated Object support. */

/* objc_setAssociatedObject() options */

enum {

OBJC_ASSOCIATION_ASSIGN = 0,

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,

OBJC_ASSOCIATION_COPY_NONATOMIC = 3,

OBJC_ASSOCIATION_RETAIN = 01401,

OBJC_ASSOCIATION_COPY = 01403

};

這些與你可以在@property語法中傳遞的選項相匹配。

混合 vTable 分發

如果你看一下現代運行時代碼,你會看到這個(在 objc-runtime-new.m)。

/***********************************************************************

*vtable dispatch

**每個類都有一個 vtable 指針。vtable 是一個 IMP 數組,

*所有的類的 vtable 中表示的選擇器數量都是相同的。(i.e.

*沒有一個類有更大或更小的 vtable).

*每個 vtable 索引都有一個關聯的蹦床,該蹦床在接收者類的

*vtable 的該索引處分派給 IMP(檢查 NULL 后)。分派

*fixup 使用了蹦床而不是 objc_msgSend.

*脆弱性:vtable 的大小和選擇器列表在啟動時已經設定好了。

*編譯器生成的代碼無法依賴于任何特定的vtable配置,甚至

*根本不使用 vtable 調度。

*內存大小:如果一個類的 vtable 和它的父類相同(i.e. 該類

*沒有重寫任何 vtable 選擇器), 那么這個類直接指向它的父

*類的 vtable。這意味著被選中包含在 vtable 中的選擇器應

*該有以下特點:

*(1) 經常被調用,但是 (2) 不經常被重寫。

*特別的是,-dealloc 是一個壞的選擇。

*轉發: 如果一個類沒有實現 vtable 中的部分選擇器, 這個類的

*vtable 中的這些選擇器的 IMP 會被設置成 objc_msgSend。

*+initialize: 每個類保持默認的 vtable(總是重定向到

*objc_msgSend)直到其 +initialize 初始化方法完成。否則,

*一個類的第一個消息可能是一個 vtable 調度,而 vtable

*蹦床不包括 +initialize 初始化檢查。

*改變: Categories, addMethod, 和 setImplementation 如果影響

*到了 vtable 的選擇器,類和所有的子類的 vtable 都將強制重建。

**********************************************************************/

這背后的思想是,運行時試圖在這個 vtable 里面存儲最常被調用的選擇器,這可以給 app 加速,因為這比 objc_msgSend 使用了更少的指令。這個 vtable 包含 16 個最常被調用的選擇器,占據了絕大部分全局調用的選擇器。你可以看到垃圾回收 app 和非垃圾回收 app 的默認選擇器都是什么。

static const char * const defaultVtable[] = {

"allocWithZone:",

"alloc",

"class",

"self",

"isKindOfClass:",

"respondsToSelector:",

"isFlipped",

"length",

"objectForKey:",

"count",

"objectAtIndex:",

"isEqualToString:",

"isEqual:",

"retain",

"release",

"autorelease",

};

static const char * const defaultVtableGC[] = {

"allocWithZone:",

"alloc",

"class",

"self",

"isKindOfClass:",

"respondsToSelector:",

"isFlipped",

"length",

"objectForKey:",

"count",

"objectAtIndex:",

"isEqualToString:",

"isEqual:",

"hash",

"addObject:",

"countByEnumeratingWithState:objects:count:",

};

那么你怎么知道是否使用了 vtable 中的方法了呢?你會在調試的堆棧跟蹤中看到以下幾個方法。這些方法你可以看成調試版的 objc_msgSend()。

objc_msgSend_fixup 代表 runtime 調用一個方法并正要把它加入到 vtable 中。

objc_msgSend_fixedup 代表你調用方法曾經在 vtable 中,現在已經不在里面了。

objc_msgSend_vtable[0-15] 代表上述 vtable 中的一個常用方法。runtime 可以隨意分配或取消它想要的值。所以這一次 objc_msgSend_vtable10 對應于 -length 方法,下一次運行可能對應方法就變了。

總結

我希望你喜歡這些,這篇文章大體上組成了我在我給 Des Moines Cocoaheads 的 ObjC 演講中提到的內容。ObjC 運行時寫的很棒,它提供了許多我們在 Cocoa / Objective-C 中習以為常的特性。如果你還沒看過 Apple 的 ObjC 運行時文檔,希望你去看一看。謝謝!

網易云免費體驗館,0成本體驗20+款云產品!

更多網易研發、產品、運營經驗分享請訪問網易云社區。

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

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

相關文章

菜鳥學習MVC實錄:弄清項目各類庫的作用和用法

MVC模式即:模型(Model)-視圖(View)-控制器(Controller) Model (模型):是應用程序中用于處理應用程序數據邏輯的部分。通常模型對象負責數據庫中存取數據View…

SSL服務器

2019獨角獸企業重金招聘Python工程師標準>>> SSL 是一個安全協議,它提供使用 TCP/IP 的通信應用程序間的隱私與完整性。因特網的 超文本傳輸協議(HTTP)使用 SSL 來實現安全的通信。 在客戶端與服務器間傳輸的數據是通過使用對稱算…

微軟Skype Translator將支持阿拉伯語即時語音翻譯

據美國科技時代網(Tech Times)3月9日報道,日前,微軟旗下即時翻譯軟件Skype Translator再添新語種,微軟宣布Skype Translator已經支持阿拉伯語。Skype用戶可通過使用阿拉伯語即時翻譯與朋友、家人以及海外商業伙伴進行交流。 據報道&#xff0…

是什么讓.NET7的Min和Max方法性能暴增了45倍?

簡介在之前的一篇文章.NET性能系列文章一:.NET7的性能改進中我們聊到Linq中的Min()和Max()方法.NET7比.NET6有高達45倍的性能提升,當時Benchmark代碼和結果如下所示:[Params(1000)] public int Length { get; set; }private int[] arr;[Globa…

html標記語言 --框架

html標記語言 --框架六、框架1、什么是框架 框架將瀏覽器劃分成不同的部分&#xff0c;每一部分加載不同的網頁 實現同一瀏覽器窗口中加載多個頁面的效果。 語法格式<frameset>.......</frameset>2. 屬性2.1 cols使用“像素數”和%分割左右窗口&#xff0c;“*” 表…

c語言兔子洞,數據結構水題選講 - osc_y08db3kb的個人空間 - OSCHINA - 中文開源技術交流社區...

[Ynoi2011]ODT\(O(nlog^2n)\) 的做法非常顯然直接把樹重鏈剖分一下&#xff0c;每個點維護輕兒子的平衡樹就行但是這題 \(1e6\) 的數據范圍使得 \(O(nlog^2n)\) 沒那么容易卡過去(當然很多人卡過去了考慮給一個點很多重兒子那么若一個點有 \(k\) 個重兒子&#xff0c;修改復雜度…

centos 7.x systemd service 配置方法整理

一、存放路徑/etc/systemd/system二、service配置整理2.1 zookeeper.service[Unit]DescriptionZooKeeper ServiceAftersyslog.targetAfternetwork.target[Service]#使用shell腳本啟動的要用forking模式TypeforkingUserzookeeperGroupzookeeper#腳本啟動ExecStart/usr/local/zoo…

MAVEN集成測試環境搭建

1. MAVEN SVN HUDSON SONAR集成測試環境搭建、1.1 軟件準備 Hudson、Jenkins、Sonar1.2 軟件安裝 說明&#xff1a;本例均使用將應用程序部署至web容器下&#xff0c;Hudson和Sonar有其他部署啟動方式&#xff0c;如有需要請自行使用&#xff0c;本文不做贅述。1.2.1 安裝hu…

ubus c語言例子,openwrt之ubus例子

好一個icrootLEDE:/# ubus call test_ubus helloworld {"id":1,"msg":"hi","array":["a","b"]}{"id": 1,"msg": "hi","shuzu": ["a","b"]}文件目…

使用Spring訪問Mongodb的方法大全——Spring Data MongoDB查詢指南

1.概述 Spring Data MongoDB 是Spring框架訪問mongodb的神器&#xff0c;借助它可以非常方便的讀寫mongo庫。本文介紹使用Spring Data MongoDB來訪問mongodb數據庫的幾種方法&#xff1a; 使用Query和Criteria類JPA自動生成的查詢方法使用Query 注解基于JSON查詢在開始前&#…

mysqldump導出備份數據庫報Table ‘performance_schema.session_variables‘ doesn‘t exist

今天在bash進行本地數據庫往云端數據庫導數據的時候&#xff0c;在本地導出.sql文件這第一步就出現了錯誤問題&#xff0c;導出sql文件的命令&#xff1a; 1 mysqldump -u 用戶名 -p 數據庫名 > xxx.sql 在做這一步將數據導出的時候報了這么一個錯誤&#xff0c; 1 mysqldu…

在Identity框架中使用RoleBasedAuthorization

本文將介紹在 Identity 框架中如何使用 Sang.AspNetCore.RoleBasedAuthorization[1] 庫。核心介紹Identity 和 jwt 的基本配置我們在這里不再贅述&#xff0c;可以參考最后的項目樣例。核心的代碼主要為 IRolePermission 的實現。internal class MyRolePermission : IRolePermi…

2016年印度公有云服務市場將達13億美元

根據IT咨詢公司Gartner最新調查數據顯示&#xff0c;2016年印度公有云服務市場預計將增長35.9%&#xff0c;達到13億美元。 增長最快的是云系統基礎設施即服務&#xff08;IaaS&#xff09;&#xff0c;2016年預計將增長45.5%&#xff1b;其次是平臺即服務&#xff08;PaaS&…

PAT 1042. 字符統計

1042. 字符統計 請編寫程序&#xff0c;找出一段給定文字中出現最頻繁的那個英文字母。 輸入格式&#xff1a; 輸入在一行中給出一個長度不超過1000的字符串。字符串由ASCII碼表中任意可見字符及空格組成&#xff0c;至少包含1個英文字母&#xff0c;以回車結束&#xff08;回車…

Magicodes.IE 2.7.0-beta發布

2.7.0-beta2022.10.27使用SixLabors.ImageSharp替代System.Drawing&#xff0c;感謝linch90 &#xff08;見pr#454&#xff09;2.6.92022.10.26fix: 動態數據源導出到多個sheet的問題 &#xff08;見#449&#xff09;2.6.82022.10.18Excel模板導出添加API&#xff0c;以支持通過…

光伏逆變器“領跑”:不止于技術

從無到有&#xff0c;從效率比拼到突破99%&#xff0c;在跟進速度上沒話說的國內光伏逆變器企業難免深陷“價格戰”、同質化的泥潭。隨著“領跑者”計劃躍居光伏主流&#xff0c;嗅到市場紅利的企業再次蜂擁而至。 目前&#xff0c;鑒衡認證發布的第一批光伏并網逆變器“領跑者…

Ubuntu 18.04上Qmmp安裝教程

Qmmp&#xff0c;一個開源的基于Qt的多媒體播放器。它具有多種音頻文件格式支持&#xff0c;DSP效果&#xff0c;視覺效果;輸出系統支持&#xff08;OSS4&#xff08;FreeBSD&#xff09;&#xff0c;ALSA&#xff08;Linux&#xff09;&#xff0c;Pulse Audio&#xff0c;JAC…

android自動跑馬燈,Android-最強跑馬燈

Android--最強跑馬燈Android 跑馬燈已經有很多版本&#xff0c;從最基本的TextView&#xff0c;到重寫TextView使TextView取消焦點限制&#xff0c;還有重寫TextView利用ScrollTo方法寫的&#xff0c;基本都能滿足一般需要。然而在使用過程中&#xff0c;發現一些意外---有時會…

python:軟件目錄結構規范

為什么要設計好目錄結構&#xff1f; “設計項目目錄結構”&#xff0c;就和“代碼編碼風格”一樣&#xff0c;屬于個人風格問題。對于這種風格上的規范&#xff0c;一直都存在兩種態度&#xff1a; 1.一種認為&#xff0c;這種個人風格問題“無關緊要”。理由是能讓程序work就…

開啟智能生活新時代 河北省智慧社區建設從各個擊破

智慧社區作為智慧城市的重要組成部分&#xff0c;是城市智慧落地的觸點&#xff0c;是城市管理、政務服務和市場服務的載體。隨著智慧城市的推廣以及新一代技術的普及&#xff0c;智慧社區的項目必將迎來新一輪的快速發展。2016年智慧社區成為企業業務落地的承載點&#xff0c;…