上篇文章:Runtime在工做中的運用編程
objc在向一個對象發送消息時,runtime會根據對象的isa指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是一直到根類還沒找到,轉向攔截調用,走消息轉發機制,一旦找到 ,就去執行它的實現
IMP
。數組
詳解:請看Runtime在工做中的運用 第二章Runtime消息機制;安全
若是向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,因此不會出現任何錯誤。也不會崩潰。bash
詳解: 若是一個方法返回值是一個對象,那麼發送給nil的消息將返回0(nil);ide
若是方法返回值爲指針類型,其指針大小爲小於或者等於sizeof(void*) ,float,double,long double 或者long long的整型標量,發送給nil的消息將返回0;函數
若是方法返回值爲結構體,發送給nil的消息將返回0。結構體中各個字段的值將都是0;佈局
若是方法的返回值不是上述提到的幾種狀況,那麼發送給nil的消息的返回值將是未定義的。post
objc_msgSend()
函數之間有什麼關係?在objc編譯時,[obj foo] 會被轉意爲:
objc_msgSend(obj, @selector(foo));
。測試
詳解:請看Runtime在工做中的運用 第二章Runtime消息機制;ui
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是,在最頂層的父類中依然找不到相應的方法時,會進入消息轉發階段,若是消息三次轉發流程仍未實現,則程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX 。
詳解:請看Runtime在工做中的運用 第三章Runtime方法調用流程;
不能向編譯後獲得的類中增長實例變量;
能向運行時建立的類中添加實例變量;
1.由於編譯後的類已經註冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內存大小已經肯定,同時runtime會調用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.因此不能向存在的類中添加實例變量。
2.運行時建立的類是能夠添加實例變量,調用class_addIvar函數. 可是的在調用 objc_allocateClassPair 以後,objc_registerClassPair 以前,緣由同上.
instance_size :實例的內存大小;objc_ivar_list *ivars:屬性列表
指向他的類對象,從而能夠找到對象上的方法
詳解:下圖很好的描述了對象,類,元類之間的關係:
圖中實線是 super_class指針,虛線是isa指針。
下面的代碼輸出什麼?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
複製代碼
NSStringFromClass([self class]) = Son NSStringFromClass([super class]) = Son
詳解:這個題目主要是考察關於 Objective-C 中對 self 和 super 的理解。
self 是類的隱藏參數,指向當前調用方法的這個類的實例;
super 本質是一個編譯器標示符,和 self 是指向的同一個消息接受者。不一樣點在於:super 會告訴編譯器,當調用方法時,去調用父類的方法,而不是本類中的方法。
當使用 self 調用方法時,會從當前類的方法列表中開始找,若是沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。而後調用父類的這個方法。
在調用[super class]
的時候,runtime會去調用objc_msgSendSuper
方法,而不是objc_msgSend
;
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
複製代碼
在objc_msgSendSuper方法中,第一個參數是一個objc_super的結構體,這個結構體裏面有兩個變量,一個是接收消息的receiver,一個是當前類的父類super_class。
objc_msgSendSuper的工做原理應該是這樣的: 從objc_super結構體指向的superClass父類的方法列表開始查找selector,找到後以objc->receiver去調用父類的這個selector。注意,最後的調用者是objc->receiver,而不是super_class!
那麼objc_msgSendSuper最後就轉變成:
// 注意這裏是從父類開始msgSend,而不是從本類開始
objc_msgSend(objc_super->receiver, @selector(class))
/// Specifies an instance of a class. 這是類的一個實例
__unsafe_unretained id receiver;
// 因爲是實例調用,因此是減號方法
- (Class)class {
return object_getClass(self);
}
複製代碼
因爲找到了父類NSObject裏面的class方法的IMP,又由於傳入的入參objc_super->receiver = self。self就是son,調用class,因此父類的方法class執行IMP以後,輸出仍是son,最後輸出兩個都同樣,都是輸出son。
每個類對象中都一個方法列表,方法列表中記錄着方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,經過這個方法名稱就能夠在方法列表中找到對應的方法實現.
_objc_msgForward
是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward
會嘗試作消息轉發。
詳解:_objc_msgForward
在進行消息轉發的過程當中會涉及如下這幾個方法:
resolveInstanceMethod:
方法 (或 resolveClassMethod:
)。forwardingTargetForSelector:
方法methodSignatureForSelector:
方法forwardInvocation:
方法doesNotRecognizeSelector:
方法具體請見:請看Runtime在工做中的運用 第三章Runtime方法調用流程;
runtime 對註冊的類會進行佈局,對於 weak 修飾的對象會放入一個 hash 表中。 用 weak 指向的對象內存地址做爲 key,當此對象的引用計數爲0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那麼就會以a爲鍵, 在這個 weak 表中搜索,找到全部以a爲鍵的 weak 對象,從而設置爲 nil。
更細一點的回答:
1.初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。
2.添加引用時:objc_initWeak函數會調用objc_storeWeak() 函數, objc_storeWeak() 的做用是更新指針指向,建立對應的弱引用表。
3.釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取全部weak指針地址的數組,而後遍歷這個數組把其中的數據設爲nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。
SideTable結構體是負責管理類的引用計數表和weak表,
詳解:參考自《Objective-C高級編程》一書 1.初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。
{
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
複製代碼
當咱們初始化一個weak變量時,runtime會調用 NSObject.mm 中的objc_initWeak函數。
// 編譯器的模擬代碼
id obj1;
objc_initWeak(&obj1, obj);
/*obj引用計數變爲0,變量做用域結束*/
objc_destroyWeak(&obj1);
複製代碼
經過objc_initWeak
函數初始化「附有weak修飾符的變量(obj1)」,在變量做用域結束時經過objc_destoryWeak
函數釋放該變量(obj1)。
2.添加引用時:objc_initWeak函數會調用objc_storeWeak() 函數, objc_storeWeak() 的做用是更新指針指向,建立對應的弱引用表。
objc_initWeak
函數將「附有weak修飾符的變量(obj1)」初始化爲0(nil)後,會將「賦值對象」(obj)做爲參數,調用objc_storeWeak
函數。
obj1 = 0;
obj_storeWeak(&obj1, obj);
複製代碼
也就是說:
weak 修飾的指針默認值是 nil (在Objective-C中向nil發送消息是安全的)
而後obj_destroyWeak
函數將0(nil)做爲參數,調用objc_storeWeak
函數。
objc_storeWeak(&obj1, 0);
複製代碼
前面的源代碼與下列源代碼相同。
// 編譯器的模擬代碼
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用計數變爲0,被置nil ... */
objc_storeWeak(&obj1, 0);
複製代碼
objc_storeWeak
函數把第二個參數的賦值對象(obj)的內存地址做爲鍵值,將第一個參數__weak修飾的屬性變量(obj1)的內存地址註冊到 weak 表中。若是第二個參數(obj)爲0(nil),那麼把變量(obj1)的地址從weak表中刪除。
因爲一個對象可同時賦值給多個附有__weak修飾符的變量中,因此對於一個鍵值,可註冊多個變量的地址。
能夠把objc_storeWeak(&a, b)
理解爲:objc_storeWeak(value, key)
,而且當key變nil,將value置nil。在b非nil時,a和b指向同一個內存地址,在b變nil時,a變nil。此時向a發送消息不會崩潰:在Objective-C中向nil發送消息是安全的。
3.釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取全部weak指針地址的數組,而後遍歷這個數組把其中的數據設爲nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。
當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程以下:
1.調用objc_release
2.由於對象的引用計數爲0,因此執行dealloc
3.在dealloc中,調用了_objc_rootDealloc函數
4.在_objc_rootDealloc中,調用了object_dispose函數
5.調用objc_destructInstance
6.最後調用objc_clear_deallocating
對象被釋放時調用的objc_clear_deallocating函數:
1.從weak表中獲取廢棄對象的地址爲鍵值的記錄
2.將包含在記錄中的全部附有 weak修飾符變量的地址,賦值爲nil
3.將weak表中該記錄刪除
4.從引用計數表中刪除廢棄對象的地址爲鍵值的記錄
總結:
其實Weak表是一個hash(哈希)表,Key是weak所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象指針的地址)數組。
下面代碼輸出什麼?
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
複製代碼
1000
詳解:
在isKindOfClass
中有一個循環,先判斷class
是否等於meta class
,不等就繼續循環判斷是否等於meta class
的super class
,不等再繼續取super class
,如此循環下去。
[NSObject class]
執行完以後調用isKindOfClass
,第一次判斷先判斷NSObject
和 NSObject
的meta class
是否相等,以前講到meta class
的時候放了一張很詳細的圖,從圖上咱們也能夠看出,NSObject
的meta class
與自己不等。接着第二次循環判斷NSObject
與meta class
的superclass
是否相等。仍是從那張圖上面咱們能夠看到:Root class(meta)
的superclass
就是 Root class(class)
,也就是NSObject自己。因此第二次循環相等,因而第一行res1輸出應該爲YES。
同理,[Sark class]
執行完以後調用isKindOfClass
,第一次for循環,Sark的Meta Class
與[Sark class]
不等,第二次for循環,Sark Meta Class
的super class
指向的是 NSObject Meta Class
, 和Sark Class
不相等。第三次for循環,NSObject Meta Class
的super class
指向的是NSObject Class
,和 Sark Class
不相等。第四次循環,NSObject Class
的super class
指向 nil, 和 Sark Class
不相等。第四次循環以後,退出循環,因此第三行的res3輸出爲NO。
isMemberOfClass
的源碼實現是拿到本身的isa指針和本身比較,是否相等。 第二行isa 指向 NSObject
的 Meta Class
,因此和 NSObject Class
不相等。第四行,isa指向Sark的Meta Class
,和Sark Class
也不等,因此第二行res2和第四行res4都輸出NO。
不管在MRC下仍是ARC下均不須要,被關聯的對象在生命週期內要比對象自己釋放的晚不少,它們會在被 NSObject -dealloc 調用的object_dispose()方法中釋放。
詳解:
一、調用 -release :引用計數變爲零
對象正在被銷燬,生命週期即將結束.
不能再有新的 __weak 弱引用,不然將指向 nil.
調用 [self dealloc]
二、 父類調用 -dealloc
繼承關係中最直接繼承的父類再調用 -dealloc
若是是 MRC 代碼 則會手動釋放實例變量們(iVars)
繼承關係中每一層的父類 都再調用 -dealloc
>三、NSObject 調 -dealloc
只作一件事:調用 Objective-C runtime 中object_dispose() 方法
>4. 調用 object_dispose()
爲 C++ 的實例變量們(iVars)調用 destructors
爲 ARC 狀態下的 實例變量們(iVars) 調用 -release
解除全部使用 runtime Associate方法關聯的對象
解除全部 __weak 引用
調用 free()
複製代碼
簡單說就是進行方法交換
詳解:請看Runtime在工做中的運用 第五章Runtime方法交換;
在Objective-C中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector的名字。利用Objective-C的動態特性,能夠實如今運行時偷換selector對應的方法實現,達到給方法掛鉤的目的。
每一個類都有一個方法列表,存放着方法的名字和方法實現的映射關係,selector的本質其實就是方法名,IMP有點相似函數指針,指向具體的Method實現,經過selector就能夠找到對應的IMP。
換方法的幾種實現方式
下面的代碼會?Compile Error
/ Runtime Crash
/ NSLog…
?
@interface NSObject (Sark)
+ (void)foo;
- (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 測試代碼
[NSObject foo];
[[NSObject new] performSelector:@selector(foo)];
複製代碼
IMP: -[NSObject(Sark) foo] ,全都正常輸出,編譯和運行都沒有問題。
詳解:
這道題和上一道題很類似,第二個調用確定沒有問題,第一個調用後會從元類中查找方法,然而方法並不在元類中,因此找元類的superclass
。方法定義在是NSObject
的Category
,因爲NSObject
的對象模型比較特殊,元類的superclass
是類對象,因此從類對象中找到了方法並調用。
霜神、iOS程序犭袁、sunnyxx