Runtime經典面試題(附答案)

上篇文章:Runtime在工做中的運用編程

1.objc在向一個對象發送消息時,發生了什麼?

objc在向一個對象發送消息時,runtime會根據對象的isa指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是一直到根類還沒找到,轉向攔截調用,走消息轉發機制,一旦找到 ,就去執行它的實現IMP數組

詳解:請看Runtime在工做中的運用 第二章Runtime消息機制;安全

2.objc中向一個nil對象發送消息將會發生什麼?

若是向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,因此不會出現任何錯誤。也不會崩潰。bash

詳解: 若是一個方法返回值是一個對象,那麼發送給nil的消息將返回0(nil);ide

若是方法返回值爲指針類型,其指針大小爲小於或者等於sizeof(void*) ,float,double,long double 或者long long的整型標量,發送給nil的消息將返回0;函數

若是方法返回值爲結構體,發送給nil的消息將返回0。結構體中各個字段的值將都是0;佈局

若是方法的返回值不是上述提到的幾種狀況,那麼發送給nil的消息的返回值將是未定義的。post

3.objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什麼關係?

在objc編譯時,[obj foo] 會被轉意爲:objc_msgSend(obj, @selector(foo));測試

詳解:請看Runtime在工做中的運用 第二章Runtime消息機制;ui

4.何時會報unrecognized selector的異常?

objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是,在最頂層的父類中依然找不到相應的方法時,會進入消息轉發階段,若是消息三次轉發流程仍未實現,則程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX 。

詳解:請看Runtime在工做中的運用 第三章Runtime方法調用流程;

5.可否向編譯後獲得的類中增長實例變量?可否向運行時建立的類中添加實例變量?爲何?

不能向編譯後獲得的類中增長實例變量;

能向運行時建立的類中添加實例變量;

1.由於編譯後的類已經註冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內存大小已經肯定,同時runtime會調用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.因此不能向存在的類中添加實例變量。

2.運行時建立的類是能夠添加實例變量,調用class_addIvar函數. 可是的在調用 objc_allocateClassPair 以後,objc_registerClassPair 以前,緣由同上.

6.給類添加一個屬性後,在類結構體裏哪些元素會發生變化?

instance_size :實例的內存大小;objc_ivar_list *ivars:屬性列表

7.一個objc對象的isa的指針指向什麼?有什麼做用?

指向他的類對象,從而能夠找到對象上的方法

詳解:下圖很好的描述了對象,類,元類之間的關係:

圖中實線是 super_class指針,虛線是isa指針。

  1. Root class (class)其實就是NSObject,NSObject是沒有超類的,因此Root class(class)的superclass指向nil。
  2. 每一個Class都有一個isa指針指向惟一的Meta class
  3. Root class(meta)的superclass指向Root class(class),也就是NSObject,造成一個迴路。
  4. 每一個Meta class的isa指針都指向Root class (meta)。

8.[self class] 與 [super class]

下面的代碼輸出什麼?

@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。

9.runtime如何經過selector找到對應的IMP地址?

每個類對象中都一個方法列表,方法列表中記錄着方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,經過這個方法名稱就能夠在方法列表中找到對應的方法實現.

10._objc_msgForward函數是作什麼的,直接調用它將會發生什麼?

_objc_msgForward是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward會嘗試作消息轉發。

詳解:_objc_msgForward在進行消息轉發的過程當中會涉及如下這幾個方法:

  1. resolveInstanceMethod:方法 (或 resolveClassMethod:)。
  2. forwardingTargetForSelector:方法
  3. methodSignatureForSelector:方法
  4. forwardInvocation:方法
  5. doesNotRecognizeSelector: 方法

具體請見:請看Runtime在工做中的運用 第三章Runtime方法調用流程;

11. runtime如何實現weak變量的自動置nil?知道SideTable嗎?

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指針的地址(這個地址的值是所指對象指針的地址)數組。

12.isKindOfClass 與 isMemberOfClass

下面代碼輸出什麼?

@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 classsuper class,不等再繼續取super class,如此循環下去。

[NSObject class]執行完以後調用isKindOfClass,第一次判斷先判斷NSObjectNSObjectmeta class是否相等,以前講到meta class的時候放了一張很詳細的圖,從圖上咱們也能夠看出,NSObjectmeta class與自己不等。接着第二次循環判斷NSObjectmeta classsuperclass是否相等。仍是從那張圖上面咱們能夠看到:Root class(meta)superclass就是 Root class(class),也就是NSObject自己。因此第二次循環相等,因而第一行res1輸出應該爲YES。

同理,[Sark class]執行完以後調用isKindOfClass,第一次for循環,Sark的Meta Class[Sark class]不等,第二次for循環,Sark Meta Classsuper class 指向的是 NSObject Meta Class, 和Sark Class不相等。第三次for循環,NSObject Meta Classsuper class指向的是NSObject Class,和 Sark Class 不相等。第四次循環,NSObject Classsuper class 指向 nil, 和 Sark Class不相等。第四次循環以後,退出循環,因此第三行的res3輸出爲NO。

isMemberOfClass的源碼實現是拿到本身的isa指針和本身比較,是否相等。 第二行isa 指向 NSObjectMeta Class,因此和 NSObject Class不相等。第四行,isa指向Sark的Meta Class,和Sark Class也不等,因此第二行res2和第四行res4都輸出NO。

13.使用runtime Associate方法關聯的對象,須要在主對象dealloc的時候釋放麼?

不管在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()
複製代碼

14. 什麼是method swizzling(俗稱黑魔法)

簡單說就是進行方法交換

詳解:請看Runtime在工做中的運用 第五章Runtime方法交換;

在Objective-C中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector的名字。利用Objective-C的動態特性,能夠實如今運行時偷換selector對應的方法實現,達到給方法掛鉤的目的。

每一個類都有一個方法列表,存放着方法的名字和方法實現的映射關係,selector的本質其實就是方法名,IMP有點相似函數指針,指向具體的Method實現,經過selector就能夠找到對應的IMP。

換方法的幾種實現方式

  • 利用 method_exchangeImplementations 交換兩個方法的實現
  • 利用 class_replaceMethod 替換方法的實現
  • 利用 method_setImplementation 來直接設置某個方法的IMP

15.Compile Error / Runtime Crash / NSLog…?

下面的代碼會?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。方法定義在是NSObjectCategory,因爲NSObject的對象模型比較特殊,元類的superclass是類對象,因此從類對象中找到了方法並調用。

感謝:

霜神、iOS程序犭袁、sunnyxx

相關文章
相關標籤/搜索