詳解Runtime執行時機制
簡介
Runtime 又叫執行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 程式碼,底層都是基於它來實現的。比如:
[receiver message];
// 底層執行時會被編譯器轉化為:
objc_msgSend(receiver, selector)
// 如果其還有引數比如:
[receiver message:(id)arg...];
// 底層執行時會被編譯器轉化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
以上你可能看不出它的價值,但是我們需要了解的是 Objective-C 是一門動態語言,它會將一些工作放在程式碼執行時才處理而並非編譯時。也就是說,有很多類和成員變數在我們編譯的時是不知道的,而在執行時,我們所編寫的程式碼會轉換成完整的確定的程式碼執行。
因此,編譯器是不夠的,我們還需要一個執行時系統(Runtime system)來處理編譯後的程式碼。
Runtime 基本是用 C 和彙編寫的,由此可見蘋果為了動態系統的高效而做出的努力。蘋果和 GNU 各自維護一個開源的 Runtime 版本,這兩個版本之間都在努力保持一致。
點選這裡下載蘋果維護的開原始碼。
Runtime 的作用
Objc 在三種層面上與 Runtime 系統進行互動:
- 通過 Objective-C 原始碼
- 通過 Foundation 框架的 NSObject 類定義的方法
- 通過對 Runtime 庫函式的直接呼叫
Objective-C 原始碼
多數情況我們只需要編寫 OC 程式碼即可,Runtime 系統自動在幕後搞定一切,還記得簡介中如果我們呼叫方法,編譯器會將 OC 程式碼轉換成執行時程式碼,在執行時確定資料結構和函式。
通過 Foundation 框架的 NSObject 類定義的方法
Cocoa 程式中絕大部分類都是 NSObject 類的子類,所以都繼承了 NSObject 的行為。(NSProxy 類時個例外,它是個抽象超類)
一些情況下,NSObject 類僅僅定義了完成某件事情的模板,並沒有提供所需要的程式碼。例如 -description
方法,該方法返回類內容的字串表示,該方法主要用來除錯程式。NSObject 類並不知道子類的內容,所以它只是返回類的名字和物件的地址,NSObject 的子類可以重新實現。
還有一些 NSObject 的方法可以從 Runtime 系統中獲取資訊,允許物件進行自我檢查。例如:
-class
方法返回物件的類;-isKindOfClass:
和-isMemberOfClass:
方法檢查物件是否存在於指定的類的繼承體系中(是否是其子類或者父類或者當前類的成員變數);-respondsToSelector:
檢查物件能否響應指定的訊息;-conformsToProtocol:
檢查物件是否實現了指定協議類的方法;-methodForSelector:
返回指定方法實現的地址。
通過對 Runtime 庫函式的直接呼叫
Runtime 系統是具有公共介面的動態共享庫。標頭檔案存放於/usr/include/objc目錄下,這意味著我們使用時只需要引入objc/Runtime.h
標頭檔案即可。
許多函式可以讓你使用純 C 程式碼來實現 Objc 中同樣的功能。除非是寫一些 Objc 與其他語言的橋接或是底層的 debug 工作,你在寫 Objc 程式碼時一般不會用到這些 C 語言函式。對於公共介面都有哪些,後面會講到。我將會參考蘋果官方的 API 文件。
一些 Runtime 的術語的資料結構
要想全面瞭解 Runtime 機制,我們必須先了解 Runtime 的一些術語,他們都對應著資料結構。
SEL
它是selector
在 Objc 中的表示(Swift 中是 Selector 類)。selector 是方法選擇器,其實作用就和名字一樣,日常生活中,我們通過人名辨別誰是誰,注意 Objc 在相同的類中不會有命名相同的兩個方法。selector 對方法名進行包裝,以便找到對應的方法實現。它的資料結構是:
typedef struct objc_selector *SEL;
我們可以看出它是個對映到方法的 C 字串,你可以通過 Objc 編譯器器命令@selector()
或者 Runtime 系統的
sel_registerName
函式來獲取一個 SEL
型別的方法選擇器。
注意:
不同類中相同名字的方法所對應的 selector 是相同的,由於變數的型別不同,所以不會導致它們呼叫方法實現混亂。
id
id 是一個引數型別,它是指向某個類的例項的指標。定義如下:
typedef struct objc_object *id;
struct objc_object { Class isa; };
以上定義,看到 objc_object
結構體包含一個 isa 指標,根據 isa 指標就可以找到物件所屬的類。
注意:
isa 指標在程式碼執行時並不總指向例項物件所屬的型別,所以不能依靠它來確定型別,要想確定型別還是需要用物件的-class
方法。
PS:KVO 的實現機理就是將被觀察物件的 isa 指標指向一箇中間類而不是真實型別,詳見:KVO章節。
Class
typedef struct objc_class *Class;
Class
其實是指向 objc_class
結構體的指標。objc_class
的資料結構如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
從 objc_class
可以看到,一個執行時類中關聯了它的父類指標、類名、成員變數、方法、快取以及附屬的協議。
其中 objc_ivar_list
和 objc_method_list
分別是成員變數列表和方法列表:
// 成員變數列表
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
// 方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
由此可見,我們可以動態修改 *methodList
的值來新增成員方法,這也是 Category 實現的原理,同樣解釋了 Category 不能新增屬性的原因。這裡可以參考下美團技術團隊的文章:深入理解 Objective-C: Category。
objc_ivar_list
結構體用來儲存成員變數的列表,而 objc_ivar
則是儲存了單個成員變數的資訊;同理,objc_method_list
結構體儲存著方法陣列的列表,而單個方法的資訊則由
objc_method
結構體儲存。
值得注意的時,objc_class
中也有一個 isa 指標,這說明 Objc 類本身也是一個物件。為了處理類和物件的關係,Runtime 庫建立了一種叫做 Meta Class(元類) 的東西,類物件所屬的類就叫做元類。Meta Class 表述了類物件本身所具備的元資料。
我們所熟悉的類方法,就源自於 Meta Class。我們可以理解為類方法就是類物件的例項方法。每個類僅有一個類物件,而每個類物件僅有一個與之相關的元類。
當你發出一個類似 [NSObject alloc](類方法)
的訊息時,實際上,這個訊息被髮送給了一個類物件(Class Object),這個類物件必須是一個元類的例項,而這個元類同時也是一個根元類(Root Meta Class)的例項。所有元類的 isa 指標最終都指向根元類。
所以當 [NSObject alloc]
這條訊息傳送給類物件的時候,執行時程式碼 objc_msgSend()
會去它元類中查詢能夠響應訊息的方法實現,如果找到了,就會對這個類物件執行方法呼叫。
上圖實現是 super_class
指標,虛線時 isa
指標。而根元類的父類是 NSObject
,isa
指向了自己。而
NSObject
沒有父類。
最後 objc_class
中還有一個 objc_cache
,快取,它的作用很重要,後面會提到。
Method
Method 代表類中某個方法的型別
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method
儲存了方法名,方法型別和方法實現:
- 方法名型別為
SEL
- 方法型別
method_types
是個 char 指標,儲存方法的引數型別和返回值型別 method_imp
指向了方法的實現,本質是一個函式指標
Ivar
Ivar
是表示成員變數的型別。
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
其中 ivar_offset
是基地址偏移位元組
IMP
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個函式指標,這是由編譯器生成的。當你發起一個 ObjC 訊息之後,最終它會執行的那段程式碼,就是由這個函式指標指定的。而 IMP
這個函式指標就指向了這個方法的實現。
如果得到了執行某個例項某個方法的入口,我們就可以繞開訊息傳遞階段,直接執行方法,這在後面 Cache
中會提到。
你會發現 IMP
指向的方法與 objc_msgSend
函式型別相同,引數都包含 id
和
SEL
型別。每個方法名都對應一個 SEL
型別的方法選擇器,而每個例項物件中的 SEL
對應的方法實現肯定是唯一的,通過一組
id
和 SEL
引數就能確定唯一的方法實現地址。
而一個確定的方法也只有唯一的一組 id
和 SEL
引數。
Cache
Cache 定義如下:
typedef struct objc_cache *Cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache 為方法呼叫的效能進行優化,每當例項物件接收到一個訊息時,它不會直接在 isa 指標指向的類的方法列表中遍歷查詢能夠響應的方法,因為每次都要查詢效率太低了,而是優先在 Cache 中查詢。
Runtime 系統會把被呼叫的方法存到 Cache 中,如果一個方法被呼叫,那麼它有可能今後還會被呼叫,下次查詢的時候就會效率更高。就像計算機組成原理中 CPU 繞過主存先訪問 Cache 一樣。
Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個更常用
可以通過class_copyPropertyList
和 protocol_copyPropertyList
方法獲取類和協議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
注意:
返回的是屬性列表,列表中每個元素都是一個objc_property_t
指標
#import <Foundation/Foundation.h>
@interface Person : NSObject
/** 姓名 */
@property (strong, nonatomic) NSString *name;
/** age */
@property (assign, nonatomic) int age;
/** weight */
@property (assign, nonatomic) double weight;
@end
以上是一個 Person 類,有3個屬性。讓我們用上述方法獲取類的執行時屬性。
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
NSLog(@"%d", outCount);
for (NSInteger i = 0; i < outCount; i++) {
NSString *name = @(property_getName(properties[i]));
NSString *attributes = @(property_getAttributes(properties[i]));
NSLog(@"%@--------%@", name, attributes);
}
列印結果如下:
2014-11-10 11:27:28.473 test[2321:451525] 3
2014-11-10 11:27:28.473 test[2321:451525] [email protected]"NSString",&,N,V_name
2014-11-10 11:27:28.473 test[2321:451525] age--------Ti,N,V_age
2014-11-10 11:27:28.474 test[2321:451525] weight--------Td,N,V_weight
property_getName
用來查詢屬性的名稱,返回 c 字串。property_getAttributes
函式挖掘屬性的真實名稱和
@encode
型別,返回 c 字串。
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
class_getProperty
和 protocol_getProperty
通過給出屬性名在類和協議中獲得屬性的引用。
訊息
一些 Runtime 術語講完了,接下來就要說到訊息了。體會蘋果官方文件中的 messages aren’t bound to method implementations until Runtime。訊息直到執行時才會與方法實現進行繫結。
這裡要清楚一點,objc_msgSend
方法看清來好像返回了資料,其實objc_msgSend
從不返回資料,而是你的方法在執行時實現被呼叫後才會返回資料。下面詳細敘述訊息傳送的步驟(如下圖):
- 首先檢測這個
selector
是不是要忽略。比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函式。 - 檢測這個
selector
的 target 是不是nil
,Objc 允許我們對一個 nil 物件執行任何方法不會 Crash,因為執行時會被忽略掉。 - 如果上面兩步都通過了,那麼就開始查詢這個類的實現
IMP
,先從 cache 裡查詢,如果找到了就執行對應的函式去執行相應的程式碼。 - 如果 cache 找不到就找類的方法列表中是否有對應的方法。
- 如果類的方法列表中找不到就到父類的方法列表中查詢,一直找到 NSObject 類為止。
- 如果還找不到,就要開始進入動態方法解析了,後面會提到。
在訊息的傳遞中,編譯器會根據情況在 objc_msgSend
, objc_msgSend_stret
,
objc_msgSendSuper
, objc_msgSendSuper_stret
這四個方法中選擇一個呼叫。如果訊息是傳遞給父類,那麼會呼叫名字帶有 Super 的函式,如果訊息返回值是資料結構而不是簡單值時,會呼叫名字帶有 stret 的函式。
方法中的隱藏引數
疑問:
我們經常用到關鍵字self
,但是self
是如何獲取當前方法的物件呢?
其實,這也是 Runtime 系統的作用,self
實在方法執行時被動態傳入的。
當 objc_msgSend
找到方法對應實現時,它將直接呼叫該方法實現,並將訊息中所有引數都傳遞給方法實現,同時,它還將傳遞兩個隱藏引數:
- 接受訊息的物件(
self
所指向的內容,當前方法的物件指標) - 方法選擇器(
_cmd
指向的內容,當前方法的 SEL 指標)
因為在原始碼方法的定義中,我們並沒有發現這兩個引數的宣告。它們時在程式碼被編譯時被插入方法實現中的。儘管這些引數沒有被明確宣告,在原始碼中我們仍然可以引用它們。
這兩個引數中, self
更實用。它是在方法實現中訪問訊息接收者物件的例項變數的途徑。
這時我們可能會想到另一個關鍵字 super
,實際上 super
關鍵字接收到訊息時,編譯器會建立一個
objc_super
結構體:
struct objc_super { id receiver; Class class; };
這個結構體指明瞭訊息應該被傳遞給特定的父類。 receiver
仍然是 self
本身,當我們想通過
[super class]
獲取父類時,編譯器其實是將指向 self
的 id
指標和
class
的 SEL 傳遞給了 objc_msgSendSuper
函式。只有在 NSObject
類中才能找到
class
方法,然後 class
方法底層被轉換為 object_getClass()
, 接著底層編譯器將程式碼轉換為
objc_msgSend(objc_super->receiver, @selector(class))
,傳入的第一個引數是指向
self
的 id
指標,與呼叫 [self class]
相同,所以我們得到的永遠都是
self
的型別。因此你會發現:
// 這句話並不能獲取父類的型別,只能獲取當前類的型別名
NSLog(@"%@", NSStringFromClass([super class]));
獲取方法地址
NSObject
類中有一個例項方法:methodForSelector
,你可以用它來獲取某個方法選擇器對應的
IMP
,舉個例子:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
當方法被當做函式呼叫時,兩個隱藏引數也必須明確給出,上面的例子呼叫了1000次函式,你也可以嘗試給 target
傳送1000次
setFilled:
訊息會花多久。
雖然可以更高效的呼叫方法,但是這種做法很少用,除非時需要持續大量重複呼叫某個方法的情況,才會選擇使用以免訊息傳送氾濫。
注意:
methodForSelector:
方法是由 Runtime 系統提供的,而不是 Objc 自身的特性
動態方法解析
你可以動態提供一個方法實現。如果我們使用關鍵字 @dynamic
在類的實現檔案中修飾一個屬性,表明我們會為這個屬性動態提供存取方法,編譯器不會再預設為我們生成這個屬性的 setter 和 getter 方法了,需要我們自己提供。
@dynamic propertyName;
這時,我們可以通過分別過載 resolveInstanceMethod:
和 resolveClassMethod:
方法新增例項方法實現和類方法實現。
當 Runtime 系統在 Cache 和類的方法列表(包括父類)中找不到要執行的方法時,Runtime 會呼叫 resolveInstanceMethod:
或
resolveClassMethod:
來給我們一次動態新增方法實現的機會。我們需要用 class_addMethod
函式完成向特定類新增特定方法實現的操作:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "[email protected]:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
上面的例子為 resolveThisMethodDynamically
方法添加了實現內容,就是 dynamicMethodIMP
方法中的程式碼。其中
"[email protected]:"
表示返回值和引數,這個符號表示的含義見:Type Encoding
注意:
動態方法解析會在訊息轉發機制侵入前執行,動態方法解析器將會首先給予提供該方法選擇器對應的IMP
的機會。如果你想讓該方法選擇器被傳送到轉發機制,就讓resolveInstanceMethod:
方法返回NO
。
訊息轉發
重定向
訊息轉發機制執行前,Runtime 系統允許我們替換訊息的接收者為其他物件。通過 - (id)forwardingTargetForSelector:(SEL)aSelector
方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
如果此方法返回 nil
或者 self
,則會計入訊息轉發機制(forwardInvocation:
),否則將向返回的物件重新發送訊息。
轉發
當動態方法解析不做處理返回 NO
時,則會觸發訊息轉發機制。這時 forwardInvocation:
方法會被執行,我們可以重寫這個方法來自定義我們的轉發邏輯:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
唯一引數是個 NSInvocation
型別的物件,該物件封裝了原始的訊息和訊息的引數。我們可以實現 forwardInvocation:
方法來對不能處理的訊息做一些處理。也可以將訊息轉發給其他物件處理,而不丟擲錯誤。
注意:引數
anInvocation 是從哪來的?
在forwardInvocation:
訊息傳送前,Runtime 系統會向物件傳送methodSignatureForSelector:
訊息,並取到返回的方法簽名用於生成 NSInvocation 物件。所以重寫forwardInvocation:
的同時也要重寫methodSignatureForSelector:
方法,否則會拋異常。
當一個物件由於沒有相應的方法實現而無法相應某訊息時,執行時系統將通過 forwardInvocation:
訊息通知該物件。每個物件都繼承了
forwardInvocation:
方法。但是, NSObject
中的方法實現只是簡單的呼叫了
doesNotRecognizeSelector:
。通過實現自己的 forwardInvocation:
方法,我們可以將訊息轉發給其他物件。
forwardInvocation:
方法就是一個不能識別訊息的分發中心,將這些不能識別的訊息轉發給不同的接收物件,或者轉發給同一個物件,再或者將訊息翻譯成另外的訊息,亦或者簡單的“吃掉”某些訊息,因此沒有響應也不會報錯。這一切都取決於方法的具體實現。
注意:
forwardInvocation:
方法只有在訊息接收物件中無法正常響應訊息時才會被呼叫。所以,如果我們嚮往一個物件將一個訊息轉發給其他物件時,要確保這個物件不能有該訊息的所對應的方法。否則,forwardInvocation:
將不可能被呼叫。
轉發和多繼承
轉發和繼承相似,可用於為 Objc 程式設計新增一些多繼承的效果。就像下圖那樣,一個物件把訊息轉發出去,就好像它把另一個物件中的方法接過來或者“繼承”過來一樣。
這使得在不同繼承體系分支下的兩個類可以實現“繼承”對方的方法,在上圖中 Warrior
和 Diplomat
沒有繼承關係,但是
Warrior
將 negotiate
訊息轉發給了 Diplomat
後,就好似
Diplomat
是 Warrior
的超類一樣。
訊息轉發彌補了 Objc 不支援多繼承的性質,也避免了因為多繼承導致單個類變得臃腫複雜。
轉發與繼承
雖然轉發可以實現繼承的功能,但是 NSObject
還是必須表面上很嚴謹,像 respondsToSelector:
和
isKindOfClass:
這類方法只會考慮繼承體系,不會考慮轉發鏈。
如果上圖中的 Warrior
物件被問到是否能響應 negotiate
訊息:
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
回答當然是 NO
, 儘管它能接受 negotiate
訊息而不報錯,因為它靠轉發訊息給 Diplomat
類響應訊息。
如果你就是想要讓別人以為 Warrior
繼承到了 Diplomat
的 negotiate
方法,你得重新實現
respondsToSelector:
和 isKindOfClass:
來加入你的轉發演算法:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
除了 respondsToSelector:
和 isKindOfClass:
之外,instancesRespondToSelector:
中也應該寫一份轉發演算法。如果使用了協議,conformsToProtocol:
同樣也要加入到這一行列中。
如果一個物件想要轉發它接受的任何遠端訊息,它得給出一個方法標籤來返回準確的方法描述 methodSignatureForSelector:
,這個方法會最終響應被轉發的訊息。從而生成一個確定的
NSInvocation
物件描述訊息和訊息引數。這個方法最終響應被轉發的訊息。它需要像下面這樣實現:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
健壯的例項變數(Non Fragile ivars)
在 Runtime 的現行版本中,最大的特點就是健壯的例項變量了。當一個類被編譯時,例項變數的記憶體佈局就形成了,它表明訪問類的例項變數的位置。例項變數一次根據自己所佔空間而產生位移:
上圖左是 NSObject
類的例項變數佈局。右邊是我們寫的類的佈局。這樣子有一個很大的缺陷,就是缺乏拓展性。哪天蘋果更新了
NSObject
類的話,就會出現問題:
我們自定義的類的區域和父類的區域重疊了。只有蘋果將父類改為以前的佈局才能拯救我們,但這樣導致它們不能再拓展它們的框架了,因為成員變數佈局被固定住了。在脆弱的例項變數(Fragile ivar)環境下,需要我們重新編譯繼承自 Apple 的類來恢復相容。如果是健壯的例項變數的話,如下圖:
在健壯的例項變數下,編譯器生成的例項變數佈局跟以前一樣,但是當 Runtime 系統檢測到與父類有部分重疊時它會調整你新新增的例項變數的位移,那樣你再子類中新新增的成員變數就被保護起來了。
注意:
在健壯的例項變數下,不要使用siof(SomeClass)
,而是用class_getInstanceSize([SomeClass class])
代替;也不要使用offsetof(SomeClass, SomeIvar)
,而要使用ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))
來代替。
總結
我們讓自己的類繼承自 NSObject
不僅僅是因為基類有很多複雜的記憶體分配問題,更是因為這使得我們可以享受到 Runtime 系統帶來的便利。
雖然平時我們很少會考慮一句簡單的呼叫方法,傳送訊息底層所做的複雜的操作,但深入理解 Runtime 系統的細節使得我們可以利用訊息機制寫出功能更強大的程式碼。
本文參考:
文/Ammar(簡書作者)
原文連結:http://www.jianshu.com/p/1e06bfee99d0
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。
相關推薦
詳解Runtime執行時機制
簡介 Runtime 又叫執行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我們平時編寫的 OC 程式碼,底層都是基於它來實現的。比如: [receiver message]; // 底層執行時會被編譯器轉化為: objc_msgSend(receive
runtime 執行時機制
runtime是一套比較底層的純C語言API, 屬於1個C語言庫, 包含了很多底層的C語言API。 在我們平時編寫的OC程式碼中, 程式執行過程時, 其實最終都是轉成了runtime的C語言程式碼, runtime算是OC的幕後工作者 比如說,下面一個建立物件的方法中, 舉例: OC : [[MJP
JVM詳解之:執行時常量池
[toc] # 簡介 JVM在執行的時候會對class檔案進行載入,連結和初始化的過程。class檔案中定義的常量池在JVM載入之後會發生什麼神奇的變化呢?快來看一看吧。 # class檔案中的常量池 之前我們在講class檔案的結構時,提到了每個class檔案都有一個常量池,常量池中存了些什麼東西呢
Java中Runtime執行時環境機制總結
最近由於在編碼中需要在java程式碼中執行linux命令,使用到了Runtime類的一些方法,也出現幾個小bug,所以趁這個機會對Runtime也就是執行時環境這個類進行總結。 Runtime.getRuntime()能得到一個Runtime物件例項,也就是當前執行時環境例
Java-異常機制詳解以及開發時異常設計的原則要求
異常處理的合理性完整性體現了一門語言是否成熟。而Java作為目前最流行的開發語言之一,固然具有一個完善的異常處理機制。本文將對異常處理機制來一次大的總結,後面講解一些原則性問題、開發技巧以及經常遇到的異常。 文章結構:1.異常機制樹的講解; 2.異常處理
iOS Runtime 執行時之三:訊息處理機制
前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別
【Spark工作機制詳解】 執行機制
Spark主要包括 排程與任務分配、I/O模組、通訊控制模組、容錯模組 、 Shuffle模組。 Spark 按照 ①應用 application ②作業 job ③ stage ④ task 四個層次進行排程,採用經典的FIFO和FAIR等排程演
OC執行時機制之Runtime
一、runtime簡介 RunTime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式。對於OC的函式,屬於動態呼叫過程,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行的時候才會根據函式的名稱找到對應的函式來呼
Hadoop偽分佈安裝詳解+MapReduce執行原理+基於MapReduce的KNN演算法實現
本篇部落格將圍繞Hadoop偽分佈安裝+MapReduce執行原理+基於MapReduce的KNN演算法實現這三個方面進行敘述。 (一)Hadoop偽分佈安裝 1、簡述Hadoop的安裝模式中–偽分佈模式與叢集模式的區別與聯絡. Hadoop的安裝方式有三種:本地模式,偽分佈模式
Python程序、執行緒、協程詳解、執行效能、效率(tqdm)
多程序實踐——multiprocessing 筆者最近在實踐多程序發現multiprocessing,真心很好用,不僅加速了運算,同時可以GPU呼叫,而且互相之間無關聯,這樣可以很放心的進行計算。 譬如(參考:多程序): from multiprocessing import Pool
ArrayBlockingQueue詳解 - 多執行緒安全性進階
1.介紹 ArrayBlockingQueue是一個阻塞式的佇列,繼承自AbstractBlockingQueue,間接的實現了Queue介面和Collection介面。底層以陣列的形式儲存資料(實際上可看作一個迴圈陣列)。常用的操作包括 add ,offer,put,remove,poll,t
webpack核心概念詳解及其執行原理
* Entry: 入口, webpack執行構建的第一步將從Entry開始,可抽象成輸入 * Module: 模組,在webpcak中一切皆模組,一個模組對應一個檔案。webpack會從配置的Entry開始遞迴找出所有依賴的模組。 * Chunk: 程式碼塊,一個Chunk由多個模組組合
梳理Python 框架之中介軟體詳解(用途和機制)
什麼是中介軟體? 中介軟體是一個Python程式設計師用來處理Django的請求和響應的框架級別的鉤子,它是一個輕量,低級別的外掛系統,用於全域性範圍內改變Django的輸入,輸出。每個中介軟體元件都負責做一些特定的功能。 說的直白一點是中介軟體就是幫我們程式設計
Java單元測試工具:JUnit4(三)——JUnit詳解之執行流程及常用註解
(三)執行流程及常用註解 這篇筆記記錄JUnit測試類執行時,類中方法的執行順序;以及JUnit中常用的註解。 1.JUnit的執行流程 1.1 新建測試類
Java執行緒詳解(3)-執行緒棧模型與執行緒的變數
要理解執行緒排程的原理,以及執行緒執行過程,必須理解執行緒棧模型。 執行緒棧是指某時刻時記憶體中執行緒排程的棧資訊,當前呼叫的方法總是位於棧頂。執行緒棧的內容是隨著程式的執行動態變化的,因此研究執行緒棧必須選擇一個執行的時刻(實際上指程式碼執行到什麼地方)。
Java執行緒詳解(4)-執行緒狀態的轉換
一、執行緒狀態 執行緒的狀態轉換是執行緒控制的基礎。執行緒狀態總的可以分為五大狀態。用一個圖來描述如下: 1、新狀態:執行緒物件已經建立,還沒有在其上呼叫start()方法。 2、可執行狀態:當執行緒有資格執行,但排程程式
Java執行緒詳解(6)-執行緒的互動
執行緒互動是比較複雜的問題,SCJP要求不很基礎:給定一個場景,編寫程式碼來恰當使用等待、通知和通知所有執行緒。 一、執行緒互動的基礎知識 SCJP所要求的執行緒互動知識點需要從java.lang.Object的類的三個方法來學習: void notif
Java執行緒詳解(7)-執行緒的排程
Java執行緒:執行緒的排程-休眠 Java執行緒排程是Java多執行緒的核心,只有良好的排程,才能充分發揮系統的效能,提高程式的執行效率。 這裡要明確的一點,不管程式設計師怎麼編寫排程,只能最大限度的影響執行緒執行的次序,而不能做到精準控
Objective-C Runtime 執行時之五:協議與分類
Objective-C中的分類允許我們通過給一個類新增方法來擴充它(但是通過category不能新增新的例項變數),並且我們不需要訪問類中的程式碼就可以做到。 Objective-C中的協議是普遍存在的介面定義方式,即在一個類中通過@protocol定義介面,在另外
Objective-C Runtime 執行時之六:拾遺
前面幾篇基本介紹了runtime中的大部分功能,包括對類與物件、成員變數與屬性、方法與訊息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。 本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏