面試題分析

load和initialize方法的調用原則和調用順序?

  1. load方法的調用時在dyld加載程序的時候調用,在main函數(shù)之前,調用順序:父類,子類,分類,如果有多個分類,看誰先編譯,編譯的順序可以通過Build Phases -> Compile Sources設置順序
  2. initialize 類第一次發(fā)送消息的時候調用,調用順序先調用父類的,在調用子類的

Runtime是什么?

Runtime是由C和C++匯編實現(xiàn)的?套API,為OC語?加?了?向對象,運?時的功能
運?時(Runtime)是指將數(shù)據(jù)類型的確定由編譯時推遲到了運?時,如類擴展和分類的區(qū)別
平時編寫的OC代碼,在程序運?過程中,其實最終會轉換成Runtime的C語?代碼,Runtime 是 Object-C 的幕后?作者

super相關面試題

@interface HFPerson : NSObject
@end
@interface HFTeacher : HFPerson
@end
@implementation HFTeacher

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"%@---%@", [self class], [super class]);
    }
    return self;
}

@end

打印的結果為
 HFTeacher---HFTeacher

是不是覺得很奇怪,[self class]打印HFTeacher能理解, [super class]就有點不能理解了
我們通過匯編來看看[super class] 底層調用的方法,前面的[self class]調用objc_msgSend方法而[super class]調用的是objc_msgSendSuper2

image.png

匯編中我們可以看到[super class]底層是通過objc_msgSendSuper2來發(fā)送消息的\

id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull 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 _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

從上面的代碼可以知道objc_msgSendSuper2的接收者在objc_super結構體里的receiver,那這個receiver會是self嗎?帶著這個疑問我們調試看看?

image.png

首先我們斷點來到這邊
然后開始lldb打印寄存器x0地址
image.png

x0地址的首地址就是self,也就是說receiver = self,接收對象還是self,也就是通過self去調用父類的方法,然后我們再來看看父類的class方法
我們已知這邊的self是實例對象,所以調用的是父類的實例方法也就是

- (Class)class {
    return object_getClass(self);
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

這邊的self是HFTeacher實例對象,所以返回的也就是實例對象的isa-> HFTeacher

內存平移

接下來我們在來看一道更有意思的面試題

@interface HFPerson : NSObject

@property (nonatomic, strong) NSString *hf_name;

- (void)saySomething;

@end

@implementation HFPerson
- (void)saySomething{
    NSLog(@"%s - %@",__func__,self.hf_name);
}

- (void)sayHello {
    NSLog(@"123123---%@", self);
}

@end
首先定義一個類HFPerson

- (void)viewDidLoad {
    [super viewDidLoad];
    HFPerson *person = [[HFPerson alloc] init];
    [person sayHello];
    Class cls = [HFPerson class];
    void *p = &cls;
    [(__bridge id)p sayHello];
}

結果輸出:
2021-08-03 14:34:35.432347+0800 內存平移[3075:666318] 123123---<HFPerson: 0x2824f0920>
2021-08-03 14:34:35.432440+0800 內存平移[3075:666318] 123123---<HFPerson: 0x16d9c1c70>

發(fā)現(xiàn)都可以調用到sayHello,接下來我們來分析一下
首先[person sayHello]是實例對象調用方法,能夠輸出是正常,但是[(__bridge id)p sayHello]為什么也能夠調用呢,我們要清楚一點的是,方法是存放在哪里,對象的方法是存放在類里面的,對象能夠找到方法是通過對象的首地址isa獲取方法。而現(xiàn)在我們Class cls = [HFPerson class] void *p = &cls 這邊的p指針指向的就是HFPerson的首地址isa,所以這邊也能找到方法并執(zhí)行調用。
接下來我們變換一下:

- (void)sayHello {
    NSLog(@"%@---%@", self, self.hf_name);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    HFPerson *person = [[HFPerson alloc] init];
    person.hf_name = @"hf";
    [person sayHello];
    Class cls = [HFPerson class];
    void *p = &cls;
    [(__bridge id)p sayHello];
}
輸出結果:
2021-08-03 14:46:57.475086+0800 內存平移[3090:669951] <HFPerson: 0x2838a3880>---hf
2021-08-03 14:46:57.475187+0800 內存平移[3090:669951] <HFPerson: 0x16f4f9c70>---<HFPerson: 0x2838a3880>

對于第一條打印結果就不解釋了,直接來看看第二條。
首先我們知道p指針指向的是HFPerson對象的isa,所以他也可以通過isa來查找到方法并調用,而這邊的hf_name是對象的屬性,self.hf_name調用的是hf_nameget方法,get方法實現(xiàn)實際就是通過實例對象的指針偏移獲取,目前我們只有一個成員變量,所以要獲取到hf_name需要偏移8個字節(jié)(前面還有個isa指針),所以p指針也會偏移8個字節(jié),但是他是往哪里偏移呢?我們來看一幅圖

image.png

通過lldb我們可以清楚知道是往高地址偏移
p指針如何往高地址偏移呢?也就是偏移后指向哪里?
我們還是lldb查看一下
image.png

lldb看到,person地址就是p地址偏移8字節(jié)后的地址。其實也不難理解,person,cls都是棧上的變量,而這邊我們也得出棧地址是從高位開始分配。
接下來我們在探究一下如果繼續(xù)往上偏移打印的會是什么呢?

@interface HFPerson : NSObject
@property (nonatomic, strong) NSString *hf_data1;
@property (nonatomic, strong) NSString *hf_name;
@end
輸出:
2021-08-03 15:12:11.497457+0800 內存平移[3119:677027] <HFPerson: 0x16da01c70>---<ViewController: 0x147006ca0>

@interface HFPerson : NSObject
@property (nonatomic, strong) NSString *hf_data1;
@property (nonatomic, strong) NSString *hf_data2;
@property (nonatomic, strong) NSString *hf_name;
@end
輸出:
2021-08-03 15:12:55.672174+0800 內存平移[3125:677782] <HFPerson: 0x16eec1c70>---ViewController

通過添加兩個成員屬性和三個屬性看到打印的結果都不一樣,兩個成員屬性,需要偏移16字節(jié),而這邊打印的是<ViewController: 0x147006ca0>三個屬性的時候偏移24個字節(jié)打印的是ViewController,<ViewController: 0x147006ca0>似乎是個對象,而ViewController似乎是個類名。首先我們要先知道目前棧里面到底有哪些對象。viewDidLoad函數(shù)隱含著兩個參數(shù):selfsel,而這兩個參數(shù)都是會入棧的,[super viewDidLoad]; 這邊我們知道實際調用的是objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...),所以棧里面會有struct objc_super * super這個參數(shù).
所以剛剛的<ViewController: 0x147006ca0>和ViewController應該是struct objc_super * super的值,這個我們可以通過lldb來驗證一下

image.png

通過lldb我們就可以看到偏移16個字節(jié)的時候打印的<HFPerson: 0x16f065c70>---<ViewController: 0x101300b30>就是struct objc_super * super里面的receiver當我們添加三個成員屬性的時候打印的是struct objc_super * super里面的super_class。
可能這時候又有一個疑問,ViewController的super_class不是UIViewController嗎?
image.png

注意:這邊評估注釋:/* super_class is the first class to search */,super_class是第一個開始查找的類,并沒有說是父類,而第一個開始查找的類是當前類。
通過上面分析我們也可以得出,結構體成員變量入棧順序是從后面往前,也就是越后面的成員越先入棧用一幅圖來表示
image.png

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容