日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ObjectTive C语言语法,[译]理解 Objective-C 运行时(下篇)

發布時間:2023/12/4 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 布局可能看起來像這樣(第一列是字節偏移量):

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

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

在不脆弱的 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+款云產品!

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

總結

以上是生活随笔為你收集整理的ObjectTive C语言语法,[译]理解 Objective-C 运行时(下篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。